From 13412d5d55a7c80ed2aaa8702e5b13edb241bc92 Mon Sep 17 00:00:00 2001 From: infytvcode <104983807+infytvcode@users.noreply.github.com> Date: Tue, 22 Nov 2022 14:03:46 +0530 Subject: [PATCH 01/17] Adding local run command --- devcontainer.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/devcontainer.md b/devcontainer.md index 824356d802b..84b4151d0ec 100644 --- a/devcontainer.md +++ b/devcontainer.md @@ -65,3 +65,6 @@ above each test function. - Remote container commands popup by clicking on _Dev Container: Go_ at bottom left - `F1` -> type _rebuild container_ to restart with a fresh container - `F1` -> `^`-`\`` to toggle terminal + + +PBS_GDPR_DEFAULT_VALUE=0 go run main.go --alsologtostderr \ No newline at end of file From 6d7d54a840fccf9999cf734042d75939c90de0e3 Mon Sep 17 00:00:00 2001 From: infytvcode <104983807+infytvcode@users.noreply.github.com> Date: Tue, 22 Nov 2022 14:58:41 +0530 Subject: [PATCH 02/17] Adding bidder --- adapters/infytvhb/infytvhb.go | 137 +++++++++ adapters/infytvhb/infytvhb_test.go | 21 ++ .../infytvhb/infytvhbtest/exemplary/app.json | 258 ++++++++++++++++ .../infytvhbtest/exemplary/video.json | 280 ++++++++++++++++++ .../supplemental/bad-response.json | 202 +++++++++++++ .../supplemental/empty-seatbid.json | 207 +++++++++++++ .../infytvhbtest/supplemental/status-204.json | 196 ++++++++++++ .../infytvhbtest/supplemental/status-400.json | 202 +++++++++++++ .../infytvhbtest/supplemental/status-503.json | 195 ++++++++++++ .../supplemental/unexpected-status.json | 201 +++++++++++++ adapters/infytvhb/params_test.go | 44 +++ exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_infytvhb.go | 16 + static/bidder-info/infytvhb.yaml | 17 ++ static/bidder-params/infytvhb.json | 53 ++++ 16 files changed, 2033 insertions(+) create mode 100644 adapters/infytvhb/infytvhb.go create mode 100644 adapters/infytvhb/infytvhb_test.go create mode 100644 adapters/infytvhb/infytvhbtest/exemplary/app.json create mode 100644 adapters/infytvhb/infytvhbtest/exemplary/video.json create mode 100644 adapters/infytvhb/infytvhbtest/supplemental/bad-response.json create mode 100644 adapters/infytvhb/infytvhbtest/supplemental/empty-seatbid.json create mode 100644 adapters/infytvhb/infytvhbtest/supplemental/status-204.json create mode 100644 adapters/infytvhb/infytvhbtest/supplemental/status-400.json create mode 100644 adapters/infytvhb/infytvhbtest/supplemental/status-503.json create mode 100644 adapters/infytvhb/infytvhbtest/supplemental/unexpected-status.json create mode 100644 adapters/infytvhb/params_test.go create mode 100644 openrtb_ext/imp_infytvhb.go create mode 100644 static/bidder-info/infytvhb.yaml create mode 100644 static/bidder-params/infytvhb.json diff --git a/adapters/infytvhb/infytvhb.go b/adapters/infytvhb/infytvhb.go new file mode 100644 index 00000000000..81705b493e1 --- /dev/null +++ b/adapters/infytvhb/infytvhb.go @@ -0,0 +1,137 @@ +package infytvhb + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + endpoint string +} + +// Builder builds a new instance of the Foo adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + requestJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: requestJSON, + } + + return []*adapters.RequestData{requestData}, nil +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} + } + var bidResp openrtb2.BidResponse + if infyExt, err := getImpressionExt(&internalRequest.Imp[0]); err == nil { + if infyExt.EndpointType == "VAST_URL" { + bidResp = openrtb2.BidResponse{ + ID: internalRequest.ID, + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: internalRequest.ID, + AdM: string(response.Body), + Price: infyExt.Floor, + ImpID: internalRequest.Imp[0].ID, + CID: "c_" + internalRequest.ID, + CrID: "cr_" + internalRequest.ID, + }, + }, + }, + }, + } + } else { + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + } + } + + bidsCapacity := 1 + if len(bidResp.SeatBid) > 0 { + bidsCapacity = len(bidResp.SeatBid[0].Bid) + } + bidResponse := adapters.NewBidderResponseWithBidsCapacity(bidsCapacity) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + if bidType, err := getMediaTypeForBid(&sb.Bid[i]); err == nil { + // resolveMacros(&sb.Bid[i]) + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: bidType, + }) + } + } + } + + return bidResponse, nil +} + +// getMediaTypeForBid determines which type of bid. +func getMediaTypeForBid(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { + return openrtb_ext.BidTypeVideo, nil +} + +// resolveMacros resolves OpenRTB macros in nurl and adm +// func resolveMacros(bid *openrtb2.Bid) { +// if bid == nil { +// return +// } +// price := strconv.FormatFloat(bid.Price, 'f', -1, 64) +// bid.NURL = strings.Replace(bid.NURL, "${AUCTION_PRICE}", price, -1) +// bid.AdM = strings.Replace(bid.AdM, "${AUCTION_PRICE}", price, -1) +// } + +// getImpressionExt parses and return first imp ext or nil +func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtInfytvHb, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: err.Error(), + } + } + + var extImpInfyTV openrtb_ext.ExtInfytvHb + if err := json.Unmarshal(bidderExt.Bidder, &extImpInfyTV); err != nil { + return nil, &errortypes.BadInput{ + Message: err.Error(), + } + } + return &extImpInfyTV, nil +} diff --git a/adapters/infytvhb/infytvhb_test.go b/adapters/infytvhb/infytvhb_test.go new file mode 100644 index 00000000000..9ca6d088015 --- /dev/null +++ b/adapters/infytvhb/infytvhb_test.go @@ -0,0 +1,21 @@ +package infytvhb + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderInfyTVHb, config.Adapter{ + Endpoint: "https://test.infy.tv/pbs/openrtb"}, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "infytvhbtest", bidder) +} diff --git a/adapters/infytvhb/infytvhbtest/exemplary/app.json b/adapters/infytvhb/infytvhbtest/exemplary/app.json new file mode 100644 index 00000000000..d4426bf7bde --- /dev/null +++ b/adapters/infytvhb/infytvhbtest/exemplary/app.json @@ -0,0 +1,258 @@ +{ + "mockBidRequest": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [ + { + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "publisherId": "IY1014", + "placementId": "1999" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": [ + "USD" + ], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://test.infy.tv/pbs/openrtb", + "body": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [ + { + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "publisherId": "IY1014", + "placementId": "1999" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": [ + "USD" + ], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "bidid": "ccbd63285c0e7b69602d90319bda6be4", + "seatbid": [ + { + "bid": [ + { + "id": "7f6c6d6ba432059a5305815a8d740283", + "impid": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "adm": "846500:00:16", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB24" + ], + "cid": "1IP31", + "crid": "1KS13", + "w": 0, + "h": 0, + "ext": { + "mediaType": "video" + } + } + ], + "seat": "1" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7f6c6d6ba432059a5305815a8d740283", + "impid": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "adm": "846500:00:16", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB24" + ], + "cid": "1IP31", + "crid": "1KS13", + "ext": { + "mediaType": "video" + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/infytvhb/infytvhbtest/exemplary/video.json b/adapters/infytvhb/infytvhbtest/exemplary/video.json new file mode 100644 index 00000000000..7eb9c80080c --- /dev/null +++ b/adapters/infytvhb/infytvhbtest/exemplary/video.json @@ -0,0 +1,280 @@ +{ + "mockBidRequest": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [ + { + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "publisherId": "IY1014", + "placementId": "1999" + } + } + } + ], + "site": { + "id": "1c94d85ea1a55ebf3325a82935240614", + "domain": "kiwilimon.com", + "cat": [ + "IAB1", + "IAB2", + "IAB3", + "IAB4", + "IAB5", + "IAB6", + "IAB7", + "IAB8", + "IAB9", + "IAB10", + "IAB11", + "IAB13", + "IAB14", + "IAB15", + "IAB17", + "IAB18" + ], + "page": "https://kiwilimon.com/", + "publisher": { + "id": "866d6b3a4a483ed4a454a1acaa588852" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": [ + "USD" + ], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://test.infy.tv/pbs/openrtb", + "body": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [ + { + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "publisherId": "IY1014", + "placementId": "1999" + } + } + } + ], + "site": { + "id": "1c94d85ea1a55ebf3325a82935240614", + "domain": "kiwilimon.com", + "cat": [ + "IAB1", + "IAB2", + "IAB3", + "IAB4", + "IAB5", + "IAB6", + "IAB7", + "IAB8", + "IAB9", + "IAB10", + "IAB11", + "IAB13", + "IAB14", + "IAB15", + "IAB17", + "IAB18" + ], + "page": "https://kiwilimon.com/", + "publisher": { + "id": "866d6b3a4a483ed4a454a1acaa588852" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": [ + "USD" + ], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "bidid": "ccbd63285c0e7b69602d90319bda6be4", + "seatbid": [ + { + "bid": [ + { + "id": "7f6c6d6ba432059a5305815a8d740283", + "impid": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "adm": "846500:00:16", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB24" + ], + "cid": "1IP31", + "crid": "1KS13", + "w": 0, + "h": 0, + "ext": { + "mediaType": "video" + } + } + ], + "seat": "1" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7f6c6d6ba432059a5305815a8d740283", + "impid": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "adm": "846500:00:16", + "adomain": [ + "test.com" + ], + "cat": [ + "IAB24" + ], + "cid": "1IP31", + "crid": "1KS13", + "ext": { + "mediaType": "video" + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/infytvhb/infytvhbtest/supplemental/bad-response.json b/adapters/infytvhb/infytvhbtest/supplemental/bad-response.json new file mode 100644 index 00000000000..5141cdca702 --- /dev/null +++ b/adapters/infytvhb/infytvhbtest/supplemental/bad-response.json @@ -0,0 +1,202 @@ +{ + "mockBidRequest": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [ + { + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": [ + "USD" + ], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://test.infy.tv/pbs/openrtb", + "body": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [ + { + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": [ + "USD" + ], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad response, json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/infytvhb/infytvhbtest/supplemental/empty-seatbid.json b/adapters/infytvhb/infytvhbtest/supplemental/empty-seatbid.json new file mode 100644 index 00000000000..0db426fc478 --- /dev/null +++ b/adapters/infytvhb/infytvhbtest/supplemental/empty-seatbid.json @@ -0,0 +1,207 @@ +{ + "mockBidRequest": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [ + { + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": [ + "USD" + ], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://test.infy.tv/pbs/openrtb", + "body": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [ + { + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": [ + "USD" + ], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "bidid": "359da97d0384d8a14767029c18fd840d", + "seatbid": [], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Empty seatbid", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/infytvhb/infytvhbtest/supplemental/status-204.json b/adapters/infytvhb/infytvhbtest/supplemental/status-204.json new file mode 100644 index 00000000000..f432e241ac1 --- /dev/null +++ b/adapters/infytvhb/infytvhbtest/supplemental/status-204.json @@ -0,0 +1,196 @@ +{ + "mockBidRequest": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [ + { + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": [ + "USD" + ], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://test.infy.tv/pbs/openrtb", + "body": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [ + { + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": [ + "USD" + ], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/infytvhb/infytvhbtest/supplemental/status-400.json b/adapters/infytvhb/infytvhbtest/supplemental/status-400.json new file mode 100644 index 00000000000..44d55575128 --- /dev/null +++ b/adapters/infytvhb/infytvhbtest/supplemental/status-400.json @@ -0,0 +1,202 @@ +{ + "mockBidRequest": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [ + { + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": [ + "USD" + ], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://test.infy.tv/pbs/openrtb", + "body": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [ + { + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": [ + "USD" + ], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 400, + "body": "The Key has a different ad format" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad Request. \"The Key has a different ad format\"", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/infytvhb/infytvhbtest/supplemental/status-503.json b/adapters/infytvhb/infytvhbtest/supplemental/status-503.json new file mode 100644 index 00000000000..310ad85e8ce --- /dev/null +++ b/adapters/infytvhb/infytvhbtest/supplemental/status-503.json @@ -0,0 +1,195 @@ +{ + "mockBidRequest": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [ + { + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": [ + "USD" + ], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://test.infy.tv/pbs/openrtb", + "body": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [ + { + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": [ + "USD" + ], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 503 + } + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/infytvhb/infytvhbtest/supplemental/unexpected-status.json b/adapters/infytvhb/infytvhbtest/supplemental/unexpected-status.json new file mode 100644 index 00000000000..bfb4b3f9b0b --- /dev/null +++ b/adapters/infytvhb/infytvhbtest/supplemental/unexpected-status.json @@ -0,0 +1,201 @@ +{ + "mockBidRequest": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [ + { + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": [ + "USD" + ], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://test.infy.tv/pbs/openrtb", + "body": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [ + { + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "video": { + "mimes": [ + "video/mp4", + "video/ogg", + "video/webm" + ], + "minduration": 3, + "maxduration": 3000, + "protocols": [ + 2, + 3, + 5, + 6, + 7, + 8 + ], + "w": 480, + "h": 320, + "linearity": 1, + "playbackmethod": [ + 2 + ], + "pos": 0 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + } + ], + "app": { + "id": "AA88S0101", + "name": "Crunchyroll", + "bundle": "com.adrise.crunchyroll", + "storeurl": "https://channelstore.roku.com/details/d11368f0934f695ff350af56600d8ccb", + "cat": [ + "IAB1" + ], + "privacypolicy": 1, + "publisher": { + "id": "IY1002", + "name": "The View Point", + "domain": "krushmedia.com" + }, + "content": { + "language": "en" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": [ + "USD" + ], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 401 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Something went wrong, please contact your Account Manager. Status Code: [ 401 ] ", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/infytvhb/params_test.go b/adapters/infytvhb/params_test.go new file mode 100644 index 00000000000..beba085a64c --- /dev/null +++ b/adapters/infytvhb/params_test.go @@ -0,0 +1,44 @@ +package infytvhb + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderInfyTV, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderInfyTV, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"publisherId": "1598991608990"}`, + `{"publisherId": "1598991608990", "placementId": "9999"}`, +} + +var invalidParams = []string{ + `{"publisherId": 42}`, + `{"publisherId": 42, "placementId":9898}`, +} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index f4d747333e3..66c98c66a8e 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -80,6 +80,7 @@ import ( "github.com/prebid/prebid-server/adapters/impactify" "github.com/prebid/prebid-server/adapters/improvedigital" "github.com/prebid/prebid-server/adapters/infytv" + "github.com/prebid/prebid-server/adapters/infytvhb" "github.com/prebid/prebid-server/adapters/inmobi" "github.com/prebid/prebid-server/adapters/interactiveoffers" "github.com/prebid/prebid-server/adapters/invibes" @@ -242,6 +243,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderImpactify: impactify.Builder, openrtb_ext.BidderImprovedigital: improvedigital.Builder, openrtb_ext.BidderInfyTV: infytv.Builder, + openrtb_ext.BidderInfyTVHb: infytvhb.Builder, openrtb_ext.BidderInMobi: inmobi.Builder, openrtb_ext.BidderInteractiveoffers: interactiveoffers.Builder, openrtb_ext.BidderInvibes: invibes.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index b4baf3d7f4b..6e924e948f6 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -162,6 +162,7 @@ const ( BidderImpactify BidderName = "impactify" BidderImprovedigital BidderName = "improvedigital" BidderInfyTV BidderName = "infytv" + BidderInfyTVHb BidderName = "infytvhb" BidderInMobi BidderName = "inmobi" BidderInteractiveoffers BidderName = "interactiveoffers" BidderInvibes BidderName = "invibes" @@ -331,6 +332,7 @@ func CoreBidderNames() []BidderName { BidderImpactify, BidderImprovedigital, BidderInfyTV, + BidderInfyTVHb, BidderInMobi, BidderInteractiveoffers, BidderInvibes, diff --git a/openrtb_ext/imp_infytvhb.go b/openrtb_ext/imp_infytvhb.go new file mode 100644 index 00000000000..fb7644e4a28 --- /dev/null +++ b/openrtb_ext/imp_infytvhb.go @@ -0,0 +1,16 @@ +package openrtb_ext + +type ExtInfytvHb struct { + DspID string `json:"dsp_id"` + CustomerID string `json:"customer_id"` + TagID string `json:"tag_id"` + EndpointID string `json:"endpoint_id"` + DealID string `json:"deal_id"` + Base string `json:"base"` + Path string `json:"path"` + DspType string `json:"dsp_type"` + MinCpm float64 `json:"min_cpm"` + MaxCpm float64 `json:"max_cpm"` + EndpointType string `json:"type"` + Floor float64 `json:"floor_price"` +} diff --git a/static/bidder-info/infytvhb.yaml b/static/bidder-info/infytvhb.yaml new file mode 100644 index 00000000000..563c506e0b1 --- /dev/null +++ b/static/bidder-info/infytvhb.yaml @@ -0,0 +1,17 @@ +maintainer: + email: tech+hb@infy.tv +modifyingVastXmlAllowed: true +endpoint: "https://nxs.infy.tv/pbs/openrtb" +capabilities: + app: + mediaTypes: + - banner + - video + - audio + - native + site: + mediaTypes: + - banner + - video + - audio + - native \ No newline at end of file diff --git a/static/bidder-params/infytvhb.json b/static/bidder-params/infytvhb.json new file mode 100644 index 00000000000..63152429d20 --- /dev/null +++ b/static/bidder-params/infytvhb.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "InfyTV_HB Adapter Params", + "description": "A schema which validates params accepted by the InfyTV adapter", + "type": "object", + "properties": { + "dsp_id": { + "type": "string", + "description": "dsp_id" + }, + "customer_id": { + "type": "string", + "description": "customer_id" + }, + "tag_id": { + "type": "string", + "description": "customer_id" + }, + "endpoint_id": { + "type": "string", + "description": "endpoint_id" + }, + "deal_id": { + "type": "string", + "description": "deal_id" + }, + "base": { + "type": "string", + "description": "customer_id" + }, + "path": { + "type": "string", + "description": "customer_id" + }, + "dsp_type": { + "type": "string", + "description": "customer_id" + }, + "min_cpm": { + "type": "number", + "description": "customer_id" + }, + "max_cpm": { + "type": "number", + "description": "customer_id" + } + }, + "required": [ + "dsp_id", + "customer_id", + "endpoint_id" + ] +} \ No newline at end of file From c94573cbd26a5da55e8d45d2dc483fcf6dc0b804 Mon Sep 17 00:00:00 2001 From: infytvcode <104983807+infytvcode@users.noreply.github.com> Date: Tue, 22 Nov 2022 19:05:13 +0530 Subject: [PATCH 03/17] Update --- adapters/infytvhb/infytvhb.go | 67 +++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/adapters/infytvhb/infytvhb.go b/adapters/infytvhb/infytvhb.go index 81705b493e1..84516ca42ed 100644 --- a/adapters/infytvhb/infytvhb.go +++ b/adapters/infytvhb/infytvhb.go @@ -25,18 +25,65 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co } func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - requestJSON, err := json.Marshal(request) - if err != nil { - return nil, []error{err} - } + var requests []*adapters.RequestData + var errors []error - requestData := &adapters.RequestData{ - Method: "POST", - Uri: a.endpoint, - Body: requestJSON, - } + for _, imp := range request.Imp { + var endpoint string + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("x-openrtb-version", "2.5") + + if request.Device != nil { + if len(request.Device.UA) > 0 { + headers.Add("User-Agent", request.Device.UA) + } + + if len(request.Device.IPv6) > 0 { + headers.Add("X-Forwarded-For", request.Device.IPv6) + } - return []*adapters.RequestData{requestData}, nil + if len(request.Device.IP) > 0 { + headers.Add("X-Forwarded-For", request.Device.IP) + } + } + + if infyExt, err := getImpressionExt(&imp); err == nil { + endpoint = infyExt.Base + + reqCopy := *request + reqCopy.Imp = []openrtb2.Imp{} + reqCopy.Test = 0 + imp.Ext = nil + imp.PMP = nil + reqCopy.Imp = append(reqCopy.Imp, imp) + reqCopy.Ext = nil + requestJSON, err := json.Marshal(reqCopy) + if err != nil { + errors = append(errors, err) + continue + } + if infyExt.EndpointType == "VAST_URL" { + requestData := &adapters.RequestData{ + Method: "GET", + Uri: endpoint, + } + requests = append(requests, requestData) + } else { + requestData := &adapters.RequestData{ + Method: "POST", + Uri: endpoint, + Body: requestJSON, + Headers: headers, + } + requests = append(requests, requestData) + } + } + + } + return requests, errors } func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { From 8de2aedbfdc31c258e3797cd04e974c27918012e Mon Sep 17 00:00:00 2001 From: infytvcode <104983807+infytvcode@users.noreply.github.com> Date: Tue, 6 Dec 2022 21:22:00 +0530 Subject: [PATCH 04/17] Updating connector --- README.md | 5 +++++ adapters/infytvhb/infytvhb.go | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e88a4b265a5..356b0c789c8 100644 --- a/README.md +++ b/README.md @@ -68,3 +68,8 @@ Or better yet, [open a pull request](https://github.com/prebid/prebid-server/com The quickest way to start developing Prebid Server in a reproducible environment isolated from your host OS is by using Visual Studio Code with [Remote Container Setup](devcontainer.md). + + + +`docker build --platform linux/x86_64 -t infytv/infy:hb-2.0.0 .` +`docker push infytv/infy:hb-2.0.0` \ No newline at end of file diff --git a/adapters/infytvhb/infytvhb.go b/adapters/infytvhb/infytvhb.go index 84516ca42ed..6861a4f4c24 100644 --- a/adapters/infytvhb/infytvhb.go +++ b/adapters/infytvhb/infytvhb.go @@ -57,7 +57,8 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte reqCopy.Imp = []openrtb2.Imp{} reqCopy.Test = 0 imp.Ext = nil - imp.PMP = nil + // imp.PMP = nil + imp.BidFloor = infyExt.Floor reqCopy.Imp = append(reqCopy.Imp, imp) reqCopy.Ext = nil requestJSON, err := json.Marshal(reqCopy) @@ -110,6 +111,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest SeatBid: []openrtb2.SeatBid{ { Bid: []openrtb2.Bid{ + //TODO: update this by parsing VAST { ID: internalRequest.ID, AdM: string(response.Body), From 7c7de1beca740675dd5d92ee6161d54e26c35ec3 Mon Sep 17 00:00:00 2001 From: infytvcode <104983807+infytvcode@users.noreply.github.com> Date: Mon, 27 Feb 2023 12:52:44 +0530 Subject: [PATCH 05/17] Adding GAM support --- adapters/infytvhb/infytvhb.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/adapters/infytvhb/infytvhb.go b/adapters/infytvhb/infytvhb.go index 6861a4f4c24..5a0a2630285 100644 --- a/adapters/infytvhb/infytvhb.go +++ b/adapters/infytvhb/infytvhb.go @@ -66,7 +66,8 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte errors = append(errors, err) continue } - if infyExt.EndpointType == "VAST_URL" { + + if infyExt.EndpointType == "VAST_URL" || infyExt.EndpointType == "GAM" { requestData := &adapters.RequestData{ Method: "GET", Uri: endpoint, From 77abfc6a6ae451ba5e1ae380c01081d0e7fdf9e3 Mon Sep 17 00:00:00 2001 From: infytvcode <104983807+infytvcode@users.noreply.github.com> Date: Thu, 11 May 2023 12:11:56 +0530 Subject: [PATCH 06/17] updating creative id logic --- adapters/infytvhb/infytvhb.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/adapters/infytvhb/infytvhb.go b/adapters/infytvhb/infytvhb.go index 5a0a2630285..2dcd712950d 100644 --- a/adapters/infytvhb/infytvhb.go +++ b/adapters/infytvhb/infytvhb.go @@ -118,8 +118,6 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest AdM: string(response.Body), Price: infyExt.Floor, ImpID: internalRequest.Imp[0].ID, - CID: "c_" + internalRequest.ID, - CrID: "cr_" + internalRequest.ID, }, }, }, From 3db96a0736361bfd3dcd38ff5cdcd4d0e19e4a20 Mon Sep 17 00:00:00 2001 From: infytvcode <104983807+infytvcode@users.noreply.github.com> Date: Fri, 12 May 2023 13:18:08 +0530 Subject: [PATCH 07/17] Added require params --- adapters/infytvhb/infytvhb.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/adapters/infytvhb/infytvhb.go b/adapters/infytvhb/infytvhb.go index 2dcd712950d..d970fe14969 100644 --- a/adapters/infytvhb/infytvhb.go +++ b/adapters/infytvhb/infytvhb.go @@ -118,6 +118,8 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest AdM: string(response.Body), Price: infyExt.Floor, ImpID: internalRequest.Imp[0].ID, + CID: "-", + CrID: "-", }, }, }, From 55d0f0536bad2f1ef3873efc565d32965803d5c8 Mon Sep 17 00:00:00 2001 From: infytvcode <104983807+infytvcode@users.noreply.github.com> Date: Thu, 1 Jun 2023 12:38:30 +0530 Subject: [PATCH 08/17] infy config --- Dockerfile | 2 +- README.md | 2 +- devcontainer.md | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 80269c908df..2679d9efd74 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ ENV CGO_ENABLED 0 COPY ./ ./ RUN go mod tidy RUN go mod vendor -ARG TEST="true" +ARG TEST="false" RUN if [ "$TEST" != "false" ]; then ./validate.sh ; fi RUN go build -mod=vendor -ldflags "-X github.com/prebid/prebid-server/version.Ver=`git describe --tags | sed 's/^v//'` -X github.com/prebid/prebid-server/version.Rev=`git rev-parse HEAD`" . diff --git a/README.md b/README.md index 77100253817..d1edc42b65f 100644 --- a/README.md +++ b/README.md @@ -72,4 +72,4 @@ is by using Visual Studio Code with [Remote Container Setup](devcontainer.md). `docker build --platform linux/x86_64 -t infytv/infy:hb-2.0.0 .` -`docker push infytv/infy:hb-2.0.0` \ No newline at end of file +`docker push infytv/infy:hb-2.0.0` diff --git a/devcontainer.md b/devcontainer.md index 84b4151d0ec..e10f44fc65a 100644 --- a/devcontainer.md +++ b/devcontainer.md @@ -67,4 +67,6 @@ above each test function. - `F1` -> `^`-`\`` to toggle terminal -PBS_GDPR_DEFAULT_VALUE=0 go run main.go --alsologtostderr \ No newline at end of file +PBS_GDPR_DEFAULT_VALUE=0 go run main.go --alsologtostderr + +export PATH=/Users/nayan/sdk/go1.19.3/bin:$PATH From ddd00e1970bfae9a1bac0d04c2dfb0d9a965fe97 Mon Sep 17 00:00:00 2001 From: infytvcode <104983807+infytvcode@users.noreply.github.com> Date: Thu, 1 Jun 2023 16:23:25 +0530 Subject: [PATCH 09/17] adding missing cid and crid --- adapters/infytvhb/infytvhb.go | 15 +++++++++++++-- go.mod | 1 + go.sum | 2 ++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/adapters/infytvhb/infytvhb.go b/adapters/infytvhb/infytvhb.go index d970fe14969..5d186447787 100644 --- a/adapters/infytvhb/infytvhb.go +++ b/adapters/infytvhb/infytvhb.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v17/openrtb2" + "github.com/prebid/openrtb/v19/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -66,7 +66,6 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte errors = append(errors, err) continue } - if infyExt.EndpointType == "VAST_URL" || infyExt.EndpointType == "GAM" { requestData := &adapters.RequestData{ Method: "GET", @@ -74,6 +73,8 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte } requests = append(requests, requestData) } else { + fmt.Printf("endpoint: %v\n", endpoint) + fmt.Printf("requestJSON: %v\n", string(requestJSON)) requestData := &adapters.RequestData{ Method: "POST", Uri: endpoint, @@ -129,6 +130,16 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } + for i, sb := range bidResp.SeatBid { + for j, b := range sb.Bid { + if b.CID == "" { + bidResp.SeatBid[i].Bid[j].CID = "-" + } + if b.CrID == "" { + bidResp.SeatBid[i].Bid[j].CrID = "-" + } + } + } } } diff --git a/go.mod b/go.mod index 14aae8de87b..48f3975a4df 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/prebid/go-gdpr v1.11.0 github.com/prebid/go-gpp v0.1.1 + github.com/prebid/openrtb/v17 v17.1.0 github.com/prebid/openrtb/v19 v19.0.0 github.com/prometheus/client_golang v1.12.1 github.com/prometheus/client_model v0.2.0 diff --git a/go.sum b/go.sum index 8e64faa69aa..ebe17330482 100644 --- a/go.sum +++ b/go.sum @@ -396,6 +396,8 @@ github.com/prebid/go-gdpr v1.11.0 h1:QbMjscuw3Ul0mDVWeMy5tP0Kii6lmTSSVhV6fm8rY9s github.com/prebid/go-gdpr v1.11.0/go.mod h1:mPZAdkRxn+iuSjaUuJAi9+0SppBOdM1PCzv/55UH3pY= github.com/prebid/go-gpp v0.1.1 h1:uTMJ+eHmKWL9WvDuxFT4LDoOeJW1yOsfWITqi49ZuY0= github.com/prebid/go-gpp v0.1.1/go.mod h1:b0TLoVln+HXFD9L9xeimxIH3FN8WDKPJ42auslxEkow= +github.com/prebid/openrtb/v17 v17.1.0 h1:sFdufdVv9zuoDLuo2/I863lSP9QlEqtZZQyDz5OXPhY= +github.com/prebid/openrtb/v17 v17.1.0/go.mod h1:nMj7j6aTIopCG91Wv3nuzcFTc7YRSOzuzdPxal+FY50= github.com/prebid/openrtb/v19 v19.0.0 h1:NA7okrg7KcvL5wEg6yI0mAyujpyfkC8XSQr3h5ocN88= github.com/prebid/openrtb/v19 v19.0.0/go.mod h1:jK+/g4Dh5vOnNl0Nh7isbZlub29aJYyrtoBkjmhzTIg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= From 2078e74656f03b2a84cd249a4a6d8d3259f7674a Mon Sep 17 00:00:00 2001 From: infytvcode <104983807+infytvcode@users.noreply.github.com> Date: Thu, 1 Jun 2023 16:41:20 +0530 Subject: [PATCH 10/17] Removed logs --- adapters/infytvhb/infytvhb.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/adapters/infytvhb/infytvhb.go b/adapters/infytvhb/infytvhb.go index 5d186447787..9f791672a86 100644 --- a/adapters/infytvhb/infytvhb.go +++ b/adapters/infytvhb/infytvhb.go @@ -73,8 +73,6 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte } requests = append(requests, requestData) } else { - fmt.Printf("endpoint: %v\n", endpoint) - fmt.Printf("requestJSON: %v\n", string(requestJSON)) requestData := &adapters.RequestData{ Method: "POST", Uri: endpoint, From 612328a8c05a63ef194b703274c5693002d0fe47 Mon Sep 17 00:00:00 2001 From: infytvcode <104983807+infytvcode@users.noreply.github.com> Date: Fri, 2 Jun 2023 12:49:53 +0530 Subject: [PATCH 11/17] improve performance --- adapters/infytvhb/infytvhb.go | 143 +++++++++++++++++----------------- 1 file changed, 72 insertions(+), 71 deletions(-) diff --git a/adapters/infytvhb/infytvhb.go b/adapters/infytvhb/infytvhb.go index 9f791672a86..c6eba3e77b5 100644 --- a/adapters/infytvhb/infytvhb.go +++ b/adapters/infytvhb/infytvhb.go @@ -28,27 +28,27 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte var requests []*adapters.RequestData var errors []error - for _, imp := range request.Imp { - var endpoint string - - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - headers.Add("Accept", "application/json") - headers.Add("x-openrtb-version", "2.5") - - if request.Device != nil { - if len(request.Device.UA) > 0 { - headers.Add("User-Agent", request.Device.UA) - } + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("x-openrtb-version", "2.5") + + if request.Device != nil { + if len(request.Device.UA) > 0 { + headers.Add("User-Agent", request.Device.UA) + } - if len(request.Device.IPv6) > 0 { - headers.Add("X-Forwarded-For", request.Device.IPv6) - } + if len(request.Device.IPv6) > 0 { + headers.Add("X-Forwarded-For", request.Device.IPv6) + } - if len(request.Device.IP) > 0 { - headers.Add("X-Forwarded-For", request.Device.IP) - } + if len(request.Device.IP) > 0 { + headers.Add("X-Forwarded-For", request.Device.IP) } + } + + for _, imp := range request.Imp { + var endpoint string if infyExt, err := getImpressionExt(&imp); err == nil { endpoint = infyExt.Base @@ -90,76 +90,77 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil - } - - if response.StatusCode == http.StatusBadRequest { + } else if response.StatusCode == http.StatusBadRequest { return nil, []error{&errortypes.BadInput{ Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), }} - } - - if response.StatusCode != http.StatusOK { + } else if response.StatusCode != http.StatusOK { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), }} } - var bidResp openrtb2.BidResponse - if infyExt, err := getImpressionExt(&internalRequest.Imp[0]); err == nil { - if infyExt.EndpointType == "VAST_URL" { - bidResp = openrtb2.BidResponse{ - ID: internalRequest.ID, - SeatBid: []openrtb2.SeatBid{ - { - Bid: []openrtb2.Bid{ - //TODO: update this by parsing VAST - { - ID: internalRequest.ID, - AdM: string(response.Body), - Price: infyExt.Floor, - ImpID: internalRequest.Imp[0].ID, - CID: "-", - CrID: "-", + + if len(internalRequest.Imp) > 0 { + var bidResp openrtb2.BidResponse + impression := &internalRequest.Imp[0] + if infyExt, err := getImpressionExt(impression); err == nil { + if infyExt.EndpointType == "VAST_URL" { + bidResp = openrtb2.BidResponse{ + ID: internalRequest.ID, + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + //TODO: update this by parsing VAST + { + ID: internalRequest.ID, + AdM: string(response.Body), + Price: infyExt.Floor, + ImpID: internalRequest.Imp[0].ID, + CID: "-", + CrID: "-", + }, }, }, }, - }, - } - } else { - if err := json.Unmarshal(response.Body, &bidResp); err != nil { - return nil, []error{err} - } - for i, sb := range bidResp.SeatBid { - for j, b := range sb.Bid { - if b.CID == "" { - bidResp.SeatBid[i].Bid[j].CID = "-" - } - if b.CrID == "" { - bidResp.SeatBid[i].Bid[j].CrID = "-" + } + } else { + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + for i, sb := range bidResp.SeatBid { + for j := range sb.Bid { + b := &bidResp.SeatBid[i].Bid[j] + if b.CID == "" { + b.CID = "-" + } + if b.CrID == "" { + b.CrID = "-" + } } } } } - } - - bidsCapacity := 1 - if len(bidResp.SeatBid) > 0 { - bidsCapacity = len(bidResp.SeatBid[0].Bid) - } - bidResponse := adapters.NewBidderResponseWithBidsCapacity(bidsCapacity) - - for _, sb := range bidResp.SeatBid { - for i := range sb.Bid { - if bidType, err := getMediaTypeForBid(&sb.Bid[i]); err == nil { - // resolveMacros(&sb.Bid[i]) - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &sb.Bid[i], - BidType: bidType, - }) + bidsCapacity := 1 + if len(bidResp.SeatBid) > 0 { + bidsCapacity = len(bidResp.SeatBid[0].Bid) + } + bidResponse := adapters.NewBidderResponseWithBidsCapacity(bidsCapacity) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + if bidType, err := getMediaTypeForBid(&sb.Bid[i]); err == nil { + // resolveMacros(&sb.Bid[i]) + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: bidType, + }) + } } } - } - return bidResponse, nil + return bidResponse, nil + } + return nil, nil } // getMediaTypeForBid determines which type of bid. From ce7ef274fcb553d813a6f95318a460a35a154dcd Mon Sep 17 00:00:00 2001 From: infytvcode <104983807+infytvcode@users.noreply.github.com> Date: Fri, 20 Oct 2023 12:52:11 +0530 Subject: [PATCH 12/17] Adding missing property --- openrtb_ext/bidders.go | 1 + 1 file changed, 1 insertion(+) diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 805ce67b272..5312b5ac4e7 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -113,6 +113,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderImpactify, BidderImprovedigital, BidderInfyTV, + BidderInfyTVHb, BidderInMobi, BidderInteractiveoffers, BidderInvibes, From 126029645e035d4e837c2313af7d6afbcdf36767 Mon Sep 17 00:00:00 2001 From: Meet Shah Date: Wed, 19 Feb 2025 15:16:38 +0530 Subject: [PATCH 13/17] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit b962448f14e348cec4cb226d5e4bb943db1cd1c9 Author: PGAMSSP <142323401+PGAMSSP@users.noreply.github.com> Date: Wed Nov 27 00:28:47 2024 +0200 PgamSSP: Add GVL vendor ID (#4056) commit 45743ca883d7b581f195ef15df534ff429762d4d Author: Copper6SSP Date: Wed Nov 27 00:16:36 2024 +0200 Copper6ssp: Add GVL vendor ID (#4072) commit 34d2cf4316f49f531e96dacc01c0bf79167f9b03 Author: Isha Bharti Date: Wed Nov 27 03:26:29 2024 +0530 Pubmatic: Declare support for OpenRTB 2.6 (#4078) commit b7229831ed8d65727c49badf776fb41113c1a97f Author: bhainesnexxen <146981622+bhainesnexxen@users.noreply.github.com> Date: Tue Nov 26 13:40:29 2024 -0800 Unruly: Remove indicated support for gzip (#4028) Co-authored-by: Brian Haines commit 748ccd05dc92c15504583bc9ce655a9cf2cf31a2 Author: Patrick McCann Date: Tue Nov 26 16:31:10 2024 -0500 Reserve IGS name (#4073) commit 56b72c1f281dcbd3bc885e1cae3e11f73a8842e1 Author: Eugene Dorfman Date: Tue Nov 26 22:13:29 2024 +0100 Pass through unknown imp.ext values to adapters (#3878) Co-authored-by: AntonYPost commit d52eb402de0fb19fc7ffb5077fa83b06e4d418f9 Author: Jeff Mahoney Date: Fri Nov 22 14:05:08 2024 -0500 Sharethrough adapter: adding declared support for oRTB 2.6 (#4066) commit 4bcf529cfedd92bb22fae6a0febe6c7ff03e07c8 Author: driftpixelai <166716541+driftpixelai@users.noreply.github.com> Date: Fri Nov 22 20:01:56 2024 +0200 Driftpixel: Add default cookie sync endpoint (#3973) commit 3f413a0868e1f0ea79876584597d90976bdec163 Author: ashishshinde-pubm <109787960+ashishshinde-pubm@users.noreply.github.com> Date: Fri Nov 22 22:59:03 2024 +0530 Modules: Add bidder response to raw bidder response stage payload (#3882) commit 95895a1b007af484f0f912de1e09c6e0719280ed Author: Nikhil Vaidya <102963966+pm-nikhil-vaidya@users.noreply.github.com> Date: Fri Nov 22 22:45:27 2024 +0530 Floors: Modify fetchrate to usefetchdatarate (#3972) commit 2099424654ccdf8fb51b2a627446c2d8bd51a6a4 Author: linux019 Date: Fri Nov 22 05:19:59 2024 +0200 Fix cache init and update timeouts in stored requests (#4027) commit f9877520cae8079bfef633501d081687a0671bbc Author: Bugxyb Date: Fri Nov 22 10:48:55 2024 +0800 AlgoriX: Add GVL vendor ID (#4068) Co-authored-by: xunyunbo commit 3406b59a231bff311f05fb9049dcf9ec5a136eb6 Author: Scott Kay Date: Thu Nov 21 15:39:59 2024 -0500 Pass "req.ext.prebid.targeting.includebrandcategory" To Bidders (#3817) commit 8b757fe4b81e67d314434b43769c4c9507d477a3 Author: Michel Chrétien Date: Thu Nov 21 17:35:09 2024 +0100 Fix: imp ext prebid adunitcode unintentionally dropped (#4064) commit 63af8af36f5deabad3ebd70f5931f37a69c55941 Author: Rafael Taveira <103446145+rafataveira@users.noreply.github.com> Date: Wed Nov 13 10:27:32 2024 -0600 New Adapter: Nativo (#3790) commit 24ba83b367de1cb313ea2e9b6f67f7b7c7a52ee2 Author: Krushmedia <71434282+Krushmedia@users.noreply.github.com> Date: Wed Nov 13 18:17:23 2024 +0200 Krushmedia: Update user sync urls (#3979) commit 181d523d2dd00dac1e517c6bfc0ae9e908f10082 Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed Nov 13 11:07:13 2024 -0500 Fix: Delete user.ext.prebid.buyeruids after extraction (#4049) commit 106f6e450a2f6ab3ce986299468b9f657f4a0826 Author: IQZoneAdx <88879712+IQZoneAdx@users.noreply.github.com> Date: Mon Nov 4 23:54:00 2024 +0200 IQzone: Add user sync (#3971) commit 34035447654e1a91ba11095c25c9d67cb51200d7 Author: PGAMSSP <142323401+PGAMSSP@users.noreply.github.com> Date: Mon Nov 4 23:30:59 2024 +0200 PgamSSP: Add currency converter (#3907) commit a788661f45ac5399cc33f656cfca7a3f1ee8d107 Author: MartinGumGum <109325501+MartinGumGum@users.noreply.github.com> Date: Mon Nov 4 11:14:07 2024 -0800 GumGum: Override the default currency (#3928) commit 6e150f36c341d006d3426d813d7beaf420d9651d Author: Scott Kay Date: Thu Oct 31 13:31:57 2024 -0400 Increment Package Version To v3 (#4029) commit 5b11f59bdde56d7b22678efb4b3dfb2da9c62a7f Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed Oct 30 15:41:51 2024 -0400 Adapters: Update to use jsonutil unmarshal (#4021) Co-authored-by: VeronikaSolovei9 commit 3907f1ae0c786fb887574134fb0edcf247976f36 Author: Scott Kay Date: Wed Oct 30 13:16:49 2024 -0400 Remove Default Request Hardcoded Aliases (#4020) commit df58baff01da6946cd7f075a10790cd1c52ea1f0 Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue Oct 29 21:05:07 2024 -0400 Cookie Sync: Use max when limit is 0 (#4022) commit db2a872e4e1ceead524d2894013f298c8b499533 Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue Oct 29 17:20:57 2024 -0400 ORTB 2.6: Full support with down convert for 2.5 adapters (#4019) Co-authored-by: hhhjort <31041505+hhhjort@users.noreply.github.com> Co-authored-by: Veronika Solovei commit 14197618936578fda6f019e21fa7b40504abc083 Author: Scott Kay Date: Tue Oct 29 16:35:05 2024 -0400 Rename Blacklisted Apps to Blocked Apps (#3620) commit ddf897c861bfb9cd026834161c43e53287120ff1 Author: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Tue Oct 29 14:07:49 2024 -0400 Update Cookie Sync Chooser Debug Message to match Java (#3558) commit a23380f23fd5792c951f0440c7a55a8e055cc839 Author: Mohammad Nurul Islam Shihan <93646635+ishihanvcs@users.noreply.github.com> Date: Tue Oct 29 21:34:31 2024 +0600 ImproveDigital: Remove placementKey & addtlconsent parsing (#3728) commit 03a4abdf04b3176fcb394ca173fedd1b6beba0ff Author: Alex Maltsev Date: Tue Oct 22 22:26:45 2024 +0300 Sample: Fix prebid js loading bug on sample html page (#3792) commit 18f679834a60cdb243afd3b7a17fd10dc020a53f Author: Sebastien Boisvert Date: Tue Oct 22 15:15:07 2024 -0400 Bump Go version to 1.22 in dev containers config (#3983) commit bcf6491f94c685fefc22f222e96c65a5cdd38344 Author: sindhuja-sridharan <148382298+sindhuja-sridharan@users.noreply.github.com> Date: Thu Oct 17 15:20:45 2024 -0600 GumGum: Declare ORTB 2.6 support (#3985) commit 451bc449e09be5aa5310e865bc3904fa92ad3474 Author: Bluesea <129151981+blueseasx@users.noreply.github.com> Date: Thu Oct 17 03:12:50 2024 +0800 BlueSea: Add site capability (#3910) Co-authored-by: prebid-bluesea commit 8134328c03de3b8d4618c027dd6caf1c063a00a6 Author: Boris Yu Date: Wed Oct 16 21:08:50 2024 +0300 Displayio: Make imp.bidfloor optional (#3959) commit b56923c28d20f05cc0b8d4d0a340233b4ed03840 Author: Piotr Jaworski <109736938+piotrj-rtbh@users.noreply.github.com> Date: Tue Oct 15 21:49:39 2024 +0200 RTB House: Resolve AUCTION_PRICE macro (#3901) commit cbe987664b8b36a63620ac9fd88ef3a4731c1892 Author: Irakli Gotsiridze Date: Tue Oct 15 23:30:17 2024 +0400 enable gzip (#3946) commit 5fcbbbfb17d4029bf60f03c7919490dfcdd98874 Author: Patrick Loughrey Date: Tue Oct 15 15:19:47 2024 -0400 Triplelift: Adding flag for 2.6 support (#3966) commit c37951a3be2494623bffbed352137eed98bf7b1b Author: ym-winston <46379634+ym-winston@users.noreply.github.com> Date: Tue Oct 15 15:18:54 2024 -0400 update yieldmo.yaml to indicate support for ortb 2.6 (#3968) commit 9bb9b3db2135b0085e8b1f3608eb1da73871e6b7 Author: bhainesnexxen <146981622+bhainesnexxen@users.noreply.github.com> Date: Tue Oct 15 12:09:33 2024 -0700 Unruly: Indicate Support for OpenRTB 2.6 (#3984) commit 87d4412b6c8e01a011c8b258274a87c37c7760bc Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed Oct 9 19:08:14 2024 +0000 Refactor: Move privacy init up (#3958) commit 64584f60f0922ef9b285089469d667424e33d395 Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue Oct 8 19:52:25 2024 +0000 Refactor: Remove getAuctionBidderRequests (#3957) commit d54c3ed22d437a590092870e611903208f840969 Author: ysfbsf Date: Tue Oct 8 15:42:40 2024 +0200 New Adapter: Missena (#3761) commit 8689e0df95b9449a22d28eb5c56bbe9fc53f719c Author: Nick Date: Mon Oct 7 10:39:59 2024 -0400 Sonobi: native and currency conversion support (#3889) Co-authored-by: bansawbanchee commit f27bcefd0ffa351a1b844f26cc5d6250fd071c64 Author: dkornet-ad <169174147+dkornet-ad@users.noreply.github.com> Date: Thu Oct 3 09:33:54 2024 +0300 New Adapter: Bidmatic (#3731) authored by @dkornet-ad commit 6c154e0f7f86d63227026506cf38872f6fe61cb1 Author: Alexander Pykhteyev Date: Wed Sep 25 23:08:23 2024 +0700 New Adapter: Streamlyn (#3900) Co-authored-by: apykhteyev commit 53f51a6668ba7c1309915e5bd3feaabcac4cd771 Author: przemkaczmarek <167743744+przemkaczmarek@users.noreply.github.com> Date: Wed Sep 25 17:53:07 2024 +0200 GPC: Set extension based on header (#3895) commit c42fe53a22b85dc0d2d651963158e92e8ce288ce Author: bkaneyama Date: Wed Sep 25 07:46:01 2024 -0700 InMobi: mtype support (#3921) commit 8b1b96e59003f7743d0e86301a87d9455e8592c1 Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed Sep 25 14:22:18 2024 +0000 Add docs build readme (#3930) commit 4462fcef3555e5d37cabecbbc12e8c98bd56ec30 Author: Eugene Dorfman Date: Wed Sep 25 16:11:45 2024 +0200 51degrees module (#3893) commit 11b6546f5bffe2737cdf0eb6c9a98d346e138504 Author: Alex Maltsev Date: Mon Sep 23 16:54:18 2024 +0300 Rubicon: Pass PBS host info to XAPI (#3903) commit 93368cc9c4327be60622dae321e06bbfdb1e971f Author: rtuschkany <35923908+rtuschkany@users.noreply.github.com> Date: Thu Sep 19 23:45:32 2024 +0200 ConnectAd: String/int fix (#3925) commit 905b3a5a29682ed74e9dc5e4c5e2404778ff9f08 Author: ShriprasadM Date: Thu Sep 19 18:30:20 2024 +0530 Log non bid reasons in bidder framework (#2891) Co-authored-by: Shriprasad Marathe Co-authored-by: ashish.shinde Co-authored-by: dhruv.sonone commit 3c4527e9790e19647f12237f59875a5bbe61cfa3 Author: Mikael Lundin Date: Wed Sep 18 13:38:49 2024 +0200 Adnuntius: Return DSA in bid response (#3777) commit 640b97c1e0c2286e98252794762926ad4fc8ce38 Author: lm-ved <105272141+lm-ved@users.noreply.github.com> Date: Mon Sep 16 12:17:54 2024 +0530 LemmaDigital: change endpoint (#3862) commit 3e24be7950bea48b960a3d68206083e4995cd97b Author: Alexander Pykhteyev Date: Sat Sep 14 04:04:09 2024 +0700 New Adapter: TGM (#3848) Co-authored-by: apykhteyev commit ffdd75f6fe1f24cf8ed7891b6af7b65afbda9761 Author: Robert Kawecki Date: Thu Sep 12 21:52:51 2024 +0200 New Adapter: AdTonos (#3853) commit c02ee8c13280f72e173fee2adae441b92fb218ae Author: Laurentiu Badea Date: Thu Sep 12 12:34:46 2024 -0700 Update exchange json tests with correct hb_pb_cat_dur (#3836) commit 7613ff5a2aa304522854ac1b61dafd780caea8d7 Author: Laurentiu Badea Date: Thu Sep 12 12:27:07 2024 -0700 Update adapter json test framework to validate BidVideo (#3835) commit 6cbedf0319f6b82946bfde02f8cdc4875f518a69 Author: schubert-sc <144821265+schubert-sc@users.noreply.github.com> Date: Wed Sep 11 19:41:53 2024 +0300 Smartx: Declare OpenRTB 2.6 support (#3896) commit e0a21d0c355ccc6cc903259a3435c7939151e66c Author: qt-io <104574052+qt-io@users.noreply.github.com> Date: Wed Sep 11 19:31:58 2024 +0300 QT: Add tcfeu support (#3892) Co-authored-by: qt-io commit b920cca0c426eed97e44ea17d28dfbf4dd8f868d Author: Yanivplaydigo <165155195+Yanivplaydigo@users.noreply.github.com> Date: Wed Sep 11 19:19:12 2024 +0300 Playdigo: Add tcfeu support (#3890) commit 6a011ed23d02a181d9ed487c105967ed91b98edf Author: ccorbo Date: Wed Sep 11 09:53:54 2024 -0400 Update github.com/rs/cors to v1.11.0 (#3884) Co-authored-by: Chris Corbo commit ec6a45d2b6772b32c04053504a3422c571f9fb06 Author: Steffen Müller <449563+steffenmllr@users.noreply.github.com> Date: Wed Sep 4 15:51:44 2024 +0200 Agma: Allow app.bundle to be used as selector for apps (#3780) commit 8237f7fec597ac15b893eb2f8715744cfaee4590 Author: Scott Kay Date: Tue Sep 3 14:34:56 2024 -0400 Refactor Bid Splitter Privacy Functions (#3645) commit f7caea51241fa0ac427d4789a60efbff9b36b9ab Author: Brian Schmidt Date: Fri Aug 30 06:47:45 2024 -0700 OpenX: indicate support for OpenRTB 2.6 (#3879) commit e825553a70ce9143a821c851eb7d77b2b01a2830 Author: Ben Oraki <46795400+BenOraki@users.noreply.github.com> Date: Fri Aug 30 16:36:22 2024 +0300 New Adapter: Oraki (#3839) commit 2e2b49fb166c0b6f95692a58ca662a8cf3d178a9 Author: escalax Date: Fri Aug 30 16:28:04 2024 +0300 New Adapter: Escalax (#3798) commit 4ea0e33a37c77b2167c72a1448a975d1841fb58a Author: Copper6SSP Date: Fri Aug 30 16:18:46 2024 +0300 New Adapter: Copper6SSP (#3755) commit 8d7117d948879d0cf44b2513a9beec3bf6404012 Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue Aug 27 13:23:34 2024 -0400 Revert "New Module: 51Degrees (#3650)" (#3888) This reverts commit 2606e7529f36aca8e645fc38814ba9924baba8a9. commit 84a8162205b1aacaec18ceedd9cb0b99796818b7 Author: Anand Venkatraman Date: Thu Aug 22 16:14:32 2024 +0530 PulsePoint: ortb 2.6 version and gpp support (#3874) authored by @anand-venkatraman commit bd85ba414df00ba971015773ef67ccecb6f7792c Author: Nick Llerandi Date: Thu Aug 22 02:15:12 2024 -0400 specifies ortb 2.6 support (#3) (#3876) commit 54f875981f54964410b9e5370a421f7d25af116f Author: dtbarne <7635750+dtbarne@users.noreply.github.com> Date: Wed Aug 21 07:26:30 2024 -0500 Update mobilefuse.yaml to indicate support for OpenRTB 2.6 and GPP (#3871) commit 59a5b07ed07f91fec6e51ee21e57e1de6099d4b5 Author: mwang-sticky Date: Wed Aug 21 20:26:15 2024 +0800 freewheel-adapter: support 2.6 (#3873) commit a556e2d3479d53509dffd21bd0e23689332a87a8 Author: Dubyk Danylo <45672370+CTMBNara@users.noreply.github.com> Date: Wed Aug 21 15:23:39 2024 +0300 Fix currency conversion bug. (#3867) Co-authored-by: ddubyk commit d16171226aacf9d3aeaf4426c10e499fcd57d9eb Author: Pubrise Date: Wed Aug 21 12:43:39 2024 +0300 new adapter (#3833) authored by @Pubrise commit e8509e659034f406b1aa0c728742b63ea565a04e Author: bretg Date: Wed Aug 21 05:40:26 2024 -0400 declare support for ORTB 2.6 (#3872) authored by @bretg commit 6be724459440c9e8fdc2f94eeff5df8280c8750e Author: Saar Amrani Date: Wed Aug 21 12:37:59 2024 +0300 Update Vidazoo bidder info for GPP support (#3869) commit e4bd6d3675111a973c100694af84f6764fc21f1e Author: gg-natalia <148577437+gg-natalia@users.noreply.github.com> Date: Sun Aug 18 06:52:28 2024 -0300 ADTS-455 remove video validations (#3842) authored by @gg-natalia commit 2606e7529f36aca8e645fc38814ba9924baba8a9 Author: James Rosewell Date: Fri Aug 16 14:20:22 2024 +0100 New Module: 51Degrees (#3650) Co-authored-by: James Rosewell Co-authored-by: Marin Miletic Co-authored-by: Sarana-Anna Co-authored-by: Eugene Dorfman Co-authored-by: Krasilchuk Yaroslav commit 0e9b234a8b6faa7a3a105ab5662aa047c8798574 Author: Antonios Sarhanis Date: Tue Aug 13 18:28:44 2024 +1000 Use format=prebid on adserver requests. (#3846) commit 4d64623dd680bfd9add95fbf2c65b5b224f2b64a Author: ownAdx <135326256+ownAdx-prebid@users.noreply.github.com> Date: Mon Aug 12 18:51:53 2024 +0530 OwnAdx: Bidder param and URL updates (#3813) Co-authored-by: Hina Yadav commit 2a19924a0076a6a501acb47ec42f80ba5bc19abc Author: ahmadlob <109217988+ahmadlob@users.noreply.github.com> Date: Mon Aug 12 16:09:23 2024 +0300 taboola-support-app-in-prebid-server (#3795) commit 4f177cae3b1ca96a9e9ed5145610eb6ab9ada42e Author: benben2001 <145416009+benben2001@users.noreply.github.com> Date: Mon Aug 12 21:07:38 2024 +0800 New Adapter: MeloZen (#3784) commit ce331a7d67fb26a09a4c3a15ec02fc75a2935c76 Author: Scott Kay Date: Thu Aug 8 14:28:25 2024 -0400 Fix: Aliases Of Bidders With UserSync Supports Declared Only (#3850) commit 211f13ada532b8a6bd9576dc490f4300d0393b2b Author: Ilia Medvedev Date: Thu Aug 8 17:34:27 2024 +0400 New Adapter: Filmzie (#3758) authored by @imedvedko commit cba6221243159fefc06c3a7193d9bc3cdcce1ca0 Author: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Date: Thu Aug 8 15:48:00 2024 +0300 SmartHub: add alias VimayX (#3760) commit dabc3862eec070236dd8a4bc9571e2eeee334673 Author: Yanivplaydigo <165155195+Yanivplaydigo@users.noreply.github.com> Date: Wed Aug 7 17:02:28 2024 +0300 Playdigo: Add user sync (#3797) commit 804334a3528381004c32832e0bb7a1329dfea0db Author: metax-kehan <115962296+metax-kehan@users.noreply.github.com> Date: Wed Aug 7 01:16:51 2024 +0800 New Adapter: MetaX (#3712) commit e8e2848f04a6bdfc4739ca8bebdeb5d2054ac571 Author: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Date: Tue Aug 6 20:06:13 2024 +0300 SmartHub: add alias FelixAds (#3837) authored by @SmartHubSolutions commit efc1e8b194a532605d13a457a24d44c1d7d1764d Author: andre-gielow-ttd <124626380+andre-gielow-ttd@users.noreply.github.com> Date: Tue Aug 6 12:59:15 2024 -0400 New Adapter: TheTradeDesk (#3738) commit c3d8379c0583dcfdcad53db34130290972dc7441 Author: dkornet-ad <169174147+dkornet-ad@users.noreply.github.com> Date: Tue Aug 6 17:49:42 2024 +0300 Adtelligent: ext aid type (#3676) authored by @dkornet-ad commit c50e264b3c525b74fba81e316dbab004b78e9f60 Author: Laurentiu Badea Date: Tue Aug 6 02:27:18 2024 -0700 OpenX: return cat/dur for video bids (#3834) commit 8b13ebcb54fbaf521665d696485b2b59986f7d54 Author: edandavi Date: Mon Aug 5 19:03:03 2024 +0300 consumable adapter - use configured endpoint instead of hardcoded value (#3783) authored by @edandavi commit 7c92e10701a338dfede742fcc0269ec3b62d16a0 Author: qt-io <104574052+qt-io@users.noreply.github.com> Date: Thu Aug 1 20:00:51 2024 +0300 New Adapter: QT (#3696) authored by @qt-io commit 843a81cf0fe7771b4220b9bf10dcd7a57c579169 Author: Vincent Date: Tue Jul 30 23:11:16 2024 +0200 Criteo: Add support for paapi (#3759) Co-authored-by: v.raybaud commit ed3e4a121e289526f495ccf005634b13c5e75c33 Author: Zhongshi Xi Date: Mon Jul 29 16:21:06 2024 -0400 Gracefully shutdown analytics module/runner (#3335) commit 466ff838b669f45a1de7a70d0f33fe113e3430cb Author: Ruslan S. Date: Tue Jul 30 03:51:44 2024 +0800 Smaato: Add DOOH support (#3751) commit 55094fe8f0c8621a0c595858e291db59301757d6 Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Mon Jul 29 11:36:16 2024 -0400 Fix: Triplelift Native add site.publisher nil check (#3824) commit 83bce5acd411fc2db39317dc77b53a626c985f6a Author: Aaron Price <67345931+AaronColbyPrice@users.noreply.github.com> Date: Thu Jul 25 05:58:01 2024 -0700 PBS Go: 2024 Doc updates including endpoint and ortb support version (#3724) Co-authored-by: aarprice@publicisgroupe.net commit 382f793e658906d5b1dfe244515401db7314adbf Author: rtuschkany <35923908+rtuschkany@users.noreply.github.com> Date: Thu Jul 25 14:56:01 2024 +0200 ConenctAd Adapter Update (#3715) commit 536821f25e148c063e66e09d0892ce3f1b69720e Author: Patrick Loughrey Date: Wed Jul 24 12:37:36 2024 -0400 Triplelift Native: Mapping tag_code (#3745) commit d1596c817e7080cc2e2a15e6b0b3893ef4be27f1 Author: Ruslan S. Date: Wed Jul 24 21:46:37 2024 +0800 Smaato: Change server response type (#3734) commit 115f77317c7484cf73710f86d30f4ecb9a76214a Author: BIGOAds Date: Wed Jul 24 21:34:40 2024 +0800 New Adapter: bigoad (#3711) Co-authored-by: linzhucheng commit f087eeaafec84ade9dca2b7a1851f5f73bde51df Author: Laurentiu Badea Date: Wed Jul 24 05:41:36 2024 -0700 OpenX: accept incoming string fields to support Prebid.js 9 (#3668) commit 0f5a1fe23db348a7de010f16d7b4b951f307abd4 Author: Adprime <64427228+Adprime@users.noreply.github.com> Date: Mon Jul 22 13:02:33 2024 +0300 Adprime: add userSync (#3770) Co-authored-by: Aiholkin commit da658e787f536d21769a49316d7d2c5dff39e323 Author: Tactics Technology LLC Date: Mon Jul 22 03:02:08 2024 -0700 CPMStar: add gvlVendorID (#3779) commit 0a3271dc7808d8da9d49c018deea365eaa53bc14 Author: Boris Yu Date: Mon Jul 22 12:53:25 2024 +0300 New Adapter: Displayio (#3691) authored by @xdevel commit 5fefeaaed05e9ce9feeec34b8231ac1bd025f021 Author: Tetiana Volkova <126241934+tetianaatsmaato@users.noreply.github.com> Date: Thu Jul 18 17:35:11 2024 +0200 Smaato: Add DSA support tests (#3749) commit a1b4451ac2e5b3890a46922aa8892c29f4281891 Author: MarinaZhuravlevaCriteo Date: Thu Jul 18 22:14:15 2024 +0700 Criteo: Add support for native (#3709) commit 99431ad83fa7546d8e8d60acc3feae77c815f37f Author: Abdulbaki Çam <71071893+bakicam@users.noreply.github.com> Date: Wed Jul 3 20:38:17 2024 +0300 New Adapter: Admatic (#3654) Co-authored-by: Faruk Çam commit 670e1d4d0d30fa22c46a9059c68fddbde95d701c Author: BizzClick <73241175+BizzClick@users.noreply.github.com> Date: Mon Jul 1 12:24:29 2024 +0300 Bizzclick: rename bizzclick to blasto (#3688) commit a907ecb1f13a0de23f44d8f6d5d26c0a3d5b28fc Author: Anand Venkatraman Date: Mon Jul 1 14:53:33 2024 +0530 PulsePoint: Marking cp/ct params to be either String or Int (#3677) commit 80a90dd32635f087563d5e9e51593a7313a3913b Author: IgorKulemzin <65122039+IgorKulemzin@users.noreply.github.com> Date: Mon Jul 1 11:45:41 2024 +0300 OpenWeb: Adapter Refactor + New Endpoint (#3670) Co-authored-by: Zdravko Kosanovic Co-authored-by: Dedi Sidi commit 8784615f24129f751c2e83b738180a089ac015dc Author: SiddhantAgrawal Date: Mon Jul 1 13:45:13 2024 +0530 InMobi: include iframe & native for M-Web (#3682) Co-authored-by: Siddhant Kumar Agrawal Co-authored-by: Siddhant Kumar Agrawal commit 09a3dd4c3d9005babe88868c1fdc029f630cf857 Author: Saar Amrani Date: Mon Jul 1 10:54:47 2024 +0300 New Adapter: Vidazoo (#3698) commit 057e25d6ab740d636dd27a2f1d6d3ae90c349427 Author: sergeykcointraffic <158448507+sergeykcointraffic@users.noreply.github.com> Date: Sat Jun 29 01:01:56 2024 +0800 New Adapter: Сointraffic (#3647) Co-authored-by: Aleksandr Štšepelin commit 07f3ee24245daac300565a38d62550663ac3d6ea Author: redaguermas Date: Thu Jun 27 06:45:38 2024 -0700 Nobid: Add iframe sync support (#3732) Co-authored-by: Reda Guermas commit 2a8483bce5654c19b42e890bff956a5728fe48a5 Author: Dmitry Savintsev Date: Wed Jun 26 20:29:27 2024 +0200 IX: save/restore global var in test (#3638) Signed-off-by: Dmitry S commit 3295c4119324f6d33d1d9851872cd9b2e594f3f3 Author: Scott Kay Date: Wed Jun 26 12:10:20 2024 -0400 AppNexus: Forward GPID (#3767) commit 9f2caf930dd8874ff762df95eed837a30cc0ff29 Author: Nick Jacob Date: Tue Jun 25 08:19:11 2024 -0400 add support for setting seat (bidderCode) override + demand source in bid meta (#3733) authored by @nickjacob commit fe5557528809056335e53c4959c91452fbf8cf03 Author: ownAdx <135326256+ownAdx-prebid@users.noreply.github.com> Date: Tue Jun 25 17:46:48 2024 +0530 OwnAdx: Add cookie sync (#3713) Co-authored-by: Hina Yadav commit 817afeb8d448a43c8665d436e68549df75a824ec Author: Ruslan S Date: Tue Jun 25 01:41:07 2024 +0800 Gitignore changes (#3752) commit 8500d569c4be11545b6e81fe6e2c048b8b4533a5 Author: 方思敏 <506374983@qq.com> Date: Mon Jun 24 17:43:07 2024 +0800 New Adapter : MediaGo (#3705) * Add MediaGo bidder adapter * update getBidType * MediaGo bidder add test covarage and update some logic * Change the EP's domain macro replacement to let the Publisher modify the config. * change mediago docs * change mediago docs * 1. follow Go's comment convention 2. remove the unsupported formats instead of leaving them as comments commit 4dc40058f8f117b39d975da83170bb45db6f67de Author: driftpixelai <166716541+driftpixelai@users.noreply.github.com> Date: Mon Jun 17 05:26:15 2024 -0700 New Adapter: Driftpixel (#3684) commit fb15da3ae96efbf92ca7bbd61b3c18ee15031d2d Author: Brett Bloxom <38990705+BrettBlox@users.noreply.github.com> Date: Mon Jun 17 01:54:02 2024 -0600 New Adapter: Concert (#3651) commit 2aa6aea88f798714a8f2bba1808869efd33bb067 Author: Wls-demo <67785512+Wls-demo@users.noreply.github.com> Date: Mon Jun 17 10:38:32 2024 +0300 Boldwin: update maintainer email (#3701) Co-authored-by: Wls-demo commit a812221ced16aad3f27949810f1109c39b7f63f7 Author: Dmitry Savintsev Date: Tue Jun 11 21:26:18 2024 +0200 Fix semgrep issue with dgryski Go ruleset (#3719) commit 1979036892546cedace3999037626b62fdb6a45b Author: Dmitry Savintsev Date: Tue Jun 11 19:23:39 2024 +0200 Fix golangci-lint issues (#3679) commit 2d2bf71aa3b2db5f55bc290028d5ae99252c22ec Author: Vungle-GordonTian <115982294+Vungle-GordonTian@users.noreply.github.com> Date: Tue Jun 11 22:24:50 2024 +0800 Vungle: Rename from liftoff (#3727) commit e13d786d5ec2db1ffd2206f25546f2f41ed62e78 Author: Scott Kay Date: Fri Jun 7 12:16:07 2024 -0400 Upgrade To Go 1.22 (#3686) commit 7702a78118cf56ebf2a4e998f4757b01640ef404 Author: Ruslan S Date: Wed Jun 5 19:07:22 2024 +0800 Smaato: Add ORTB2.6 tests (#3646) commit 32fdbc4be35375912ac886ca05f7318c06995e0c Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue Jun 4 21:07:45 2024 -0400 Imp FPD: Skip bidder params and native validation (#3720) commit ecf317144863d7aa9e5e7ea5c454e4c246d5ad8e Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue Jun 4 16:33:23 2024 -0400 Fix: Expect Array Overwrites in Imp FPD Tests (#3725) commit c4fde39c25918f8931c720cde5a5375578be5dc1 Author: Scott Kay Date: Tue Jun 4 13:36:18 2024 -0400 FPD: Don't Merge Arrays (#3708) commit 37bcf01d9faf8c19c7837340e04563815ea46feb Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Mon Jun 3 22:16:19 2024 -0400 Bidder-specific imp FPD (#3704) commit 2b8e8cd96d08dafcff0f4d6400602876f2660b95 Author: ym-winston <46379634+ym-winston@users.noreply.github.com> Date: Sun Jun 2 01:13:13 2024 -0400 Yieldmo: Add bid floor currency conversion (#3697) commit c528d182af1f5701b1605b2bd688594a9ddfde75 Author: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Date: Fri May 31 17:02:26 2024 +0300 new alias tredio (#3707) co-authored by @SmartHubSolutions commit ff9aed5138ad69a47d4ef8052d100bf4a89e8e6c Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed May 29 14:31:41 2024 -0400 Move imp validation into ortb package (#3685) commit 9133d2cf25d4a3ef4300495bd258a47f8997866c Author: Aman Jain <34913883+amanpatniajmer@users.noreply.github.com> Date: Mon May 27 10:59:44 2024 +0530 Medianet: Adds mtype support to be used as BidType (#3658) Co-authored-by: Aman Jain Co-authored-by: rajatgoyal2510 commit 72d040b29703e868dc95a923f974bef652ae4e6e Author: 李向军 <401154047@qq.com> Date: Thu May 23 18:35:43 2024 +0800 Yeahmobi: Fix video bug (#3680) Co-authored-by: @lxj15398019970 commit 93bf6516aab94fca26b649b05a9c97ae36a64336 Author: Zhongshi Xi Date: Thu May 23 05:28:24 2024 -0400 Hello World Sample (#3326) Co-authored-by: Bret Gorsline commit 83e12ea767fc24f24c684f0d4ae359d3ed7868e9 Author: pm-avinash-kapre <112699665+AvinashKapre@users.noreply.github.com> Date: Wed May 22 19:07:27 2024 +0530 pubmatic: Pass imp.ext.gpid to SSP (#3669) commit 884b0d0611563f9dabe84990c05e38b3d5c4401f Author: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Date: Wed May 22 16:37:14 2024 +0300 SmartHub: add jdpmedia alias (#3689) commit a939c4b16a4fd67f64f24ecbe01556155762bd43 Author: bhainesnexxen <146981622+bhainesnexxen@users.noreply.github.com> Date: Wed May 22 04:13:41 2024 -0700 Unruly: dynamic bid response array size (#3644) Co-authored-by: Brian Haines commit 01e9f6d04642a614d18407181526626a27210db9 Author: ecdrsvc <82906140+ecdrsvc@users.noreply.github.com> Date: Wed May 22 06:20:39 2024 -0400 set geoscope to CAN (#3687) commit e3ce0ecf7e6f70791d7f00f84fea81e05e7fa3af Author: SerhiiNahornyi Date: Wed May 22 12:19:39 2024 +0200 Rubicon: Remove `buyeruid` logic (#3683) commit d3f5db1b28e3e7104009901e587938da65047df6 Author: CPMStar Date: Wed May 22 03:17:37 2024 -0700 CPMStar: use iframe based usersync (#3660) commit 2c39db78b15d4fa42c4882d6be278c4716fba0cc Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Thu May 9 14:32:16 2024 -0400 Fix: Set bid meta adaptercode to adapter name (#3661) commit 6cef5f90b58ea207925112513eb9878d1c82ab90 Author: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Date: Tue May 7 08:39:46 2024 +0300 Update SmartHub adapter and add alias Markapp (#3627) authored by @SmartHubSolutions commit 692ff3a99eaa639f62141f8581fb42354f9f99d2 Author: Dmitry Savintsev Date: Mon May 6 19:12:27 2024 +0200 Fix issues flagged by golangci-lint (#3621) commit 8cce68a9556cc77ca16a8d746e49686f3d8cf40b Author: ashishshinde-pubm <109787960+ashishshinde-pubm@users.noreply.github.com> Date: Thu May 2 22:58:51 2024 +0530 Capture SeatNonBids for rejected bids due to floor enforcement (#3625) commit cee7a5719edb0f265a39599ace4b42f400399ea1 Author: bhainesnexxen <146981622+bhainesnexxen@users.noreply.github.com> Date: Thu May 2 01:42:35 2024 -0700 Unruly: bid.dur support (#3643) Co-authored-by: Brian Haines commit 6fa583e8b8c9221088bc85b548fdcadd7af13f20 Author: Yanivplaydigo <165155195+Yanivplaydigo@users.noreply.github.com> Date: Thu May 2 11:11:59 2024 +0300 Playdigo: new adapter (#3636) commit 6520bacc398e80739c1aa55c533215cbce4b7392 Author: mwang-sticky Date: Thu May 2 16:11:35 2024 +0800 Freewheel: add video dur and cat (#3640) commit bc89164de1ea415ccc8d6ff2b5aacdea161e04f2 Author: product-trustedstack <141160331+product-trustedstack@users.noreply.github.com> Date: Thu May 2 12:45:40 2024 +0530 New Adapter: Trustedstack (#3618) authored by @product-trustedstack commit 8d643626e9226212d0f8880ae7210611c7be27a8 Author: Scott Kay Date: Tue Apr 30 13:48:50 2024 -0400 Refactor Request Alias Parsing In Request Splitter (#3619) commit 49e22c5a2295daf7e5267affa9a5e427f8ae8fe0 Author: Scott Kay Date: Tue Apr 30 12:32:02 2024 -0400 Flipp + Alkimi: Minor Refactoring (#3622) commit 848572b7b2cd5532a6cdef2ef5e0cfa38b7dcdb1 Author: bretg Date: Tue Apr 30 11:46:13 2024 -0400 Mobfoxpb: Update contact (#3628) commit 98fa3aa6a405f2f7f1655cfd945f3dcf4677647c Author: PGAMSSP <142323401+PGAMSSP@users.noreply.github.com> Date: Tue Apr 30 18:25:20 2024 +0300 PgamSSP: gpp support (#3630) commit 4d8e88eca6c922efc31ff2bf9e1b61e40ffa30ac Author: ccorbo Date: Tue Apr 30 11:10:40 2024 -0400 IX: indicate support for OpenRTB 2.6 (#3642) Co-authored-by: Chris Corbo commit 74fbb38cfa4f101ebb27c91efacd60c3c02c1d40 Author: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Tue Apr 30 07:47:26 2024 -0700 Configurable Stored Request Fetch Timeout (#3614) commit b89fe4abee8e9e97f36475e7fd9c9608c46608b5 Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Mon Apr 29 16:32:01 2024 -0400 CPMStar: Fix test by specifying returned imps (#3649) commit 30082c04295370d46d57db4526c80be8f52806f2 Author: CPMStar Date: Mon Apr 29 00:42:47 2024 -0700 CPMStar: Fixed for loop index reference bug (#3604) commit 5da25e3e52e833467dc9da5e269f0d96397188d9 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri Apr 26 14:46:16 2024 -0400 Bump golang.org/x/net from 0.17.0 to 0.23.0 (#3635) commit 379445ba56e7ff850dd2357c2c045232e46120d5 Author: Dhruv Sonone <83747371+Pubmatic-Dhruv-Sonone@users.noreply.github.com> Date: Sat Apr 27 00:00:44 2024 +0530 Event tracker injection (#3339) commit 36dea2cb9dae4c792e2710b13dad4430ee8e2a31 Author: Scott Kay Date: Fri Apr 26 14:23:05 2024 -0400 Refactor FPD Merge Functions (#3601) commit ecb50e71d2ca7edd9ff7174e910703f67412b0b5 Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Thu Apr 25 13:03:41 2024 -0400 Fix: Inject default DSA before auction request is rebuilt (#3639) commit 5e7c8240a8e73e2e74707d9062bb972b7c06f4fc Author: johnwier <49074029+johnwier@users.noreply.github.com> Date: Thu Apr 25 02:37:33 2024 -0700 Conversant: Make requests in USD (#3611) Co-authored-by: johwier commit 440ba5ca14f23d5054a1950a0c77485ecf32202f Author: readpeak-user <63640438+readpeak-user@users.noreply.github.com> Date: Thu Apr 25 12:08:10 2024 +0300 New Adapter: Readpeak (#3610) Co-authored-by: Tuomo Tilli commit 74ff6ae220dbdc5a8052f48cd40638d9f7ee2dab Author: teqblaze <162988436+teqblaze@users.noreply.github.com> Date: Thu Apr 25 09:09:10 2024 +0300 New Adapter: Loyal (#3586) Co-authored-by: loyal commit 78dd64d7488848689e2e71c71ac78f6863eee3bf Author: Denis Logachov Date: Thu Apr 25 08:55:43 2024 +0300 Adkernel: bid.mtype support (#3631) commit e8ff959425081f968c7582d78556a9ac16a2e241 Author: xmgiddev <133856186+xmgiddev@users.noreply.github.com> Date: Thu Apr 25 08:49:12 2024 +0300 MgidX: Update config info (#3633) Co-authored-by: gaudeamus Co-authored-by: Evgeny Nagorny Co-authored-by: xmgiddev <> commit ca83a83f4a96f23180e4cbd0704d76ca58c4aa3a Author: Zdravko Kosanović <41286499+zkosanovic@users.noreply.github.com> Date: Mon Apr 22 12:32:40 2024 +0200 Rise: Add placementId parameter to bidder ext (#3626) commit 763551963dd3208dde6f9bf35b31ef76cc83814f Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue Apr 16 15:49:31 2024 -0400 Metrics: Add per adapter metric indicating when buyeruid was scrubbed (#3623) commit 5bfa34dd6f9b0f43d8cd2237956ff062854f834e Author: César Fernández Date: Tue Apr 16 07:53:15 2024 +0200 Axonix: Add userSync (#3598) commit 3206e99914ce259106a80d4644b64d9ab5200f09 Author: Jeff Mahoney Date: Mon Apr 15 04:08:30 2024 -0400 Update sharethrough.yaml (#3624) commit 2cc2c90a09708de6fe5cdbf5d798a08db0b806d9 Author: Saurabh Narkhede <108730956+pm-saurabh-narkhede@users.noreply.github.com> Date: Thu Apr 11 01:17:49 2024 +0530 Add ImpIds in RequestData for associated Impressions (#3364) commit 9cd1b3dcba890a6ee6daa2ba4622c0b4008ddd6a Author: Zhongshi Xi Date: Wed Apr 10 13:29:15 2024 -0400 Local stored response fetching (#3600) commit 12802fae0876ccfb328be1110eb9d433e322eca8 Author: Scott Kay Date: Tue Apr 9 18:04:29 2024 -0400 EIDs Should Not Require Unique Sources (#3607) commit b42841e94abac057f7af53b0eca6689b3d70df23 Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue Apr 9 16:26:38 2024 -0400 DSA: Inject default in bid requests if not present (#3540) commit a35a6687f25561857a21648e2423db6d2d6373f0 Author: onetag-dev <38786435+onetag-dev@users.noreply.github.com> Date: Mon Apr 8 08:14:51 2024 +0200 Onetag: add redirect userSync support (#3612) Co-authored-by: lorenzob commit e982bfebedb696f9bce57a3018cc7592cf62fec1 Author: Nilesh Chate <97721111+pm-nilesh-chate@users.noreply.github.com> Date: Thu Apr 4 00:53:25 2024 +0530 Privacy Sandbox: Topics in headers (#3393) commit fb0384bb94352f13607d22d52781e8bd01ea328d Author: psmrt <162991810+psmrt@users.noreply.github.com> Date: Wed Apr 3 11:55:34 2024 +0530 New adapter: Smrtconnect (#3571) Co-authored-by: pragnesh commit 47c9434db5a41c41bcb63cee369c7eae10db84e0 Author: Jaydeep Mohite <30924180+pm-jaydeep-mohite@users.noreply.github.com> Date: Tue Apr 2 22:50:24 2024 +0530 Floors: Update FloorsSchemaVersion data type from string to int (#3592) commit 9070008fa4d16dc71f0a33087e84d2e74c82bd8f Author: Rajesh Koilpillai Date: Mon Apr 1 23:19:11 2024 +0530 Update README.md (#3603) commit cfb9d2eaaf24bacfe369ceb3864a80ab75c2b2f4 Author: Nilesh Chate <97721111+pm-nilesh-chate@users.noreply.github.com> Date: Mon Apr 1 23:05:30 2024 +0530 Fix: TestAmpBadRequests (#3546) commit c72ffe4db7067fa8f6658493ccdbf53a1585101c Author: Veronika Solovei Date: Wed Mar 27 11:20:40 2024 -0700 Fix for NPE for nil request in analytics module (#3599) commit 67c487d1bec5dbd737ab7b8cce7047e087436456 Author: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Tue Mar 26 14:35:37 2024 -0400 Make Targeting in Response Optional (#3574) commit 25a4466577deba21f1490d883c07679fe2716948 Author: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Tue Mar 26 13:19:51 2024 -0400 Add Ext.Prebid.Analytics Support (#3563) * Setup and TODOs * updateReqWrapperForAnalytics setup * Add updateReqWrapper code to build. Update comments * Updates to handle copying * Add tests for updateReqWrapper function * Add full tests, remove unnecessary code * Remove some comments * Fix comment * Clean up * Address comments * Clone prep * fix test * Update tests, add clone request for update func * Improve tests, simplify update function * Remove secondMockAnalytics commit a6267d76911d8baeb9d9e9b3ab634496102ed8d6 Author: Aditya Mahendrakar Date: Mon Mar 25 10:23:10 2024 -0700 Make admin listener configurable (#3520) * Make admin listener configurable Admin listener is made configurable by adding `admin_enabled`. It is enabled by default. Making it configurable allows a host company to not run the listener on admin port if they desire to do so. * move Enabled to a separate Admin struct commit 94148bf12714a77c0f05b000bc98c1f7ea98f5cf Author: Dmitry Savintsev Date: Mon Mar 25 18:22:49 2024 +0100 Fix semgrep dgryski.semgrep-go issues (#3511) * fix semgrep dgryski.semgrep-go issues Fix most of the semgrep issues with the http://semgrep.dev/r/dgryski.semgrep-go ruleset (`semgrep --config http://semgrep.dev/r/dgryski.semgrep-go`). Left the issue with Content-Type text/plain on json.Encode in endpoints/openrtb2/amp_auction.go since changing to application/json breaks the AMP unit tests, and issues with the pointer receiver for MarshalJSON in usersync/cookie.go. Fix #3509. Signed-off-by: Dmitry S * add comment about legacy text/plain content type Signed-off-by: Dmitry S * fix semgrep dgryski issue with w.Write, add nosemgrep Signed-off-by: Dmitry S * add nosemgrep ignore for marshal-json-pointer-receiver --------- Signed-off-by: Dmitry S commit 54874d0dc30a286333917b296fb612e7c4ba26d6 Author: Dmitry Savintsev Date: Wed Mar 20 18:58:59 2024 +0100 add 'debug' and 'integration' to sample requests (#3575) Description in openrtb2/sample-requests/valid-whole/exemplary/all-ext.json says: "This demonstrates all of the OpenRTB extensions supported by Prebid Server." However, ext.prebid.debug and ext.prebid.integration, documented in https://docs.prebid.org/prebid-server/endpoints/openrtb2/pbs-endpoint-auction.html#openrtb-extensions are currently missing. Add them for completeness. Signed-off-by: Dmitry S commit 82ba5856f836c0910319eec94b4a6eadfc9409a2 Author: linux019 Date: Mon Mar 18 20:10:27 2024 +0200 Fix: Panic in bid adjustments (#3547) * #3543 fix panic in bids adjustment * add a test for empty ext.prebid and account with enabled bid adjustments * add more tests for bid adjustments * remove extra space --------- Co-authored-by: oaleksieiev commit 6cc298c7742154c59068eafb493838aff28bf6b1 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Mar 18 14:06:31 2024 -0400 Bump google.golang.org/protobuf from 1.30.0 to 1.33.0 (#3573) Bumps google.golang.org/protobuf from 1.30.0 to 1.33.0. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 1484a46cfd8f0019f27402a21913a485b3bce7c0 Author: JonGoSonobi Date: Mon Mar 18 08:45:22 2024 -0400 Sonobi: Added consent macros to the user sync url (#3572) commit 82cb3e84959975eef78e42fb2258003298a97c36 Author: Ashish Garg Date: Mon Mar 18 16:36:14 2024 +0530 Fix yandex properties file (#3568) commit db3415592c3126f7c76fdaa2f631aa215b2c9071 Author: Adserver.Online <61009237+adserver-online@users.noreply.github.com> Date: Mon Mar 18 07:43:29 2024 +0200 New Adapter: Aso (#3565) Co-authored-by: dev commit 4870355f3ecfe7b32b275d5bf9ab7edbb991c51f Author: linux019 Date: Wed Mar 13 19:26:48 2024 +0200 Fix loading of default bid adjustments for "account_defaults" (#3555) * Default account bid adjustments was not loaded * add a test for account_defaults.bidadjustments --------- Co-authored-by: oaleksieiev commit e78ffb48a6b7afd7be56959a6a60fa20056783de Author: Steffen Müller Date: Tue Mar 12 18:16:09 2024 +0100 New Analytics Adapter: agma (#3400) * Init agma * Updated imports from v19 to v20 * Change default url * Fixed typo in README * Fixed buffers typo in readme * Check for Market Research Puropse on Consent * Fix Typo in sruct * Removes errir check on buffer write * Extract App and Sites lookup into an extra function * Adds test for checking the StatusCode commit c13178991cbdd2ee8c62531c17295cbd85f103c1 Author: xmgiddev <133856186+xmgiddev@users.noreply.github.com> Date: Tue Mar 12 11:10:09 2024 +0200 MgidX Bid Adapter: add disabled param (#3562) Co-authored-by: gaudeamus Co-authored-by: Evgeny Nagorny Co-authored-by: xmgiddev <> commit 063101a09ba1ab43c2ba76cf60af7cec5c120b0e Author: e-volution-tech <61746103+e-volution-tech@users.noreply.github.com> Date: Tue Mar 12 11:10:03 2024 +0200 Evolution Bid Adapter: add iframe to userSync (#3561) commit 40fd4fffba95c0b919155792a6aacea498a74ce9 Author: Scott Kay Date: Mon Mar 11 13:32:11 2024 -0400 Extract FPD Merge To Separate Package (#3533) commit 0362eb44e417f0cf1d9387a3795bba671a579068 Author: dengxinjing <43231655+dengxinjing@users.noreply.github.com> Date: Thu Mar 7 17:22:42 2024 +0800 New Adapter: Roulax (#3447) Co-authored-by: dengxinjing commit e2a9806c37fdddd19b732d869ef1ab441e681a0f Author: IQZoneAdx <88879712+IQZoneAdx@users.noreply.github.com> Date: Thu Mar 7 10:25:17 2024 +0200 iqzone get media type from mtype bid property (#3557) commit aff36081afe301ea1dd39e89a35053e02935c321 Author: Piotr Jaworski <109736938+piotrj-rtbh@users.noreply.github.com> Date: Thu Mar 7 09:06:02 2024 +0100 RTB House: regional endpoints (#3551) commit 7c4f5f11bf767f946f6c0dd99a108203c0da1bd7 Author: mustafa kemal Date: Thu Mar 7 10:54:52 2024 +0300 New Adapter: Theadx (#3498) Co-authored-by: mku commit 4b1ca6b470863901bed03075ef9915acc24a018a Author: Nilesh Chate <97721111+pm-nilesh-chate@users.noreply.github.com> Date: Tue Mar 5 21:57:44 2024 +0530 Privacy Sandbox: support testing label header (#3381) commit 05a1293ec8a7a3f44f5045b9f09809868f0a477e Author: Scott Kay Date: Tue Mar 5 02:26:43 2024 -0500 Generate Bid ID Test Hardening (#3491) commit 0b5d04e78c38cdee1a7f9dfcb5958a6647cab338 Author: Dmitry Savintsev Date: Mon Mar 4 22:14:42 2024 +0100 do normal 'go vet' as it works now (#3550) commit 93137cdccb52a5e6cc58d2d773e921e09e1e9436 Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Mon Mar 4 15:59:23 2024 -0500 Convert GDPR bidder name types to string to accommodate analytics (#3554) commit 82caa3781e21728450b605a8b73381f6daafeaf7 Author: Mikael Lundin Date: Mon Mar 4 07:30:03 2024 +0100 Send site ext as key values to allow targeting and pick the first eid to solve eids for Schibsted. (#3530) commit ecdff687b32728cbc801e0e6f5ae64dcb4d20018 Author: Aman Jain <34913883+amanpatniajmer@users.noreply.github.com> Date: Mon Mar 4 11:55:45 2024 +0530 Medianet: Upgrades to OpenRTB 2.6 (#3548) Co-authored-by: Aman Jain Co-authored-by: rajatgoyal2510 commit 4fb7be0e12a85d7e8867086b696097aa5404b9b7 Author: SerhiiNahornyi Date: Fri Mar 1 07:55:07 2024 +0200 Rubicon: Remove api validation (#3493) commit 7db3d9f8be2316513c09492a40569cc3c5e89ca2 Author: dzhang-criteo <87757739+dzhang-criteo@users.noreply.github.com> Date: Thu Feb 29 14:11:09 2024 +0100 TheMediaGrid: Add GPP macros (#3545) authored by @dzhang-criteo commit e8267b8cc8376b63ff9c8498b775a56c00b83de4 Author: guscarreon Date: Wed Feb 28 14:48:36 2024 -0500 Use Json compacter in the bidders/params endpoint (#3395) commit fd920153517a33a37271c623ce5ea5ecf8f234c4 Author: dzhang-criteo <87757739+dzhang-criteo@users.noreply.github.com> Date: Wed Feb 28 14:49:22 2024 +0100 Criteo: add GPP macros (#3544) commit 9f8a9c45b00701964826000331de0eb86394a469 Author: Dmitry Savintsev Date: Tue Feb 27 23:24:19 2024 +0100 Reformat structures to use key names (#3524) Signed-off-by: Dmitry S commit 11decc200677979abc6f36763a38519dd8a626ae Author: linux019 Date: Tue Feb 27 21:51:35 2024 +0200 Fix modules template and builder (#3534) Co-authored-by: oaleksieiev commit d13dfc58e8d0c5e4f9f5412ad433f54f3e558b22 Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue Feb 27 14:27:21 2024 -0500 DSA: Bid response adrender, behalf & paid validations (#3523) commit 030da80c908945f4b23a17778c67c7a18a4ba0e6 Author: pm-avinash-kapre <112699665+AvinashKapre@users.noreply.github.com> Date: Tue Feb 27 22:00:58 2024 +0530 Fix: Pubstack memory leak (#3541) commit 17b7082a9d2d8fc2e0a6a15bc836e2e5227eeafe Author: ahmadlob <109217988+ahmadlob@users.noreply.github.com> Date: Mon Feb 26 20:46:39 2024 +0200 Taboola: Fix gpp query param (#3515) commit 14fcbb780bc649da67d927a834b425872de35ffb Author: Aditya Mahendrakar Date: Thu Feb 22 17:30:21 2024 -0800 Update prebid.org url to https (#3529) commit 1d96d891dfd7789dfd87ad37a7610f2adb31bb32 Author: Zdravko Kosanović <41286499+zkosanovic@users.noreply.github.com> Date: Fri Feb 23 05:23:52 2024 +0400 MinuteMedia: Add GPP macros (#3497) commit b945d09c608ea27cf7bd94e337a1dc775b1cde3a Author: Hendrick Musche <107099114+sag-henmus@users.noreply.github.com> Date: Fri Feb 23 02:00:35 2024 +0100 SeedingAlliance: Deprecate seatId in favor of accountId (#3486) commit c9131abdbcb18aa620fd80b0978340cb1ddb897e Author: Dmitry Savintsev Date: Thu Feb 22 21:11:21 2024 +0100 Add formatcheck Make target (#3480) Signed-off-by: Dmitry S commit 24a23d3202f26cbf68875578ffa16123775db12b Author: Zdravko Kosanović <41286499+zkosanovic@users.noreply.github.com> Date: Thu Feb 22 09:59:47 2024 +0400 Rise: Add GPP macros (#3496) commit 5ae5e05ee6f56eea9e603c1d8e1bc69c099351d0 Author: bretg Date: Thu Feb 22 00:59:30 2024 -0500 adf regional endpoints (#3503) commit 57783356f5f99d3e12478c350da396b56b636a47 Author: bretg Date: Thu Feb 22 00:51:22 2024 -0500 Update BMTM maintainer address (#3483) commit 33b466ec1dd9ed9a70220a13e81c42605c61d773 Author: Philip Watson Date: Thu Feb 22 18:50:09 2024 +1300 Stroeercore: support DSA (#3495) commit 5900d36c7769b7d341648306b76ea6bbd7fad5b3 Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed Feb 21 20:09:24 2024 -0500 DSA: Use seat nonbid code 300 (#3531) commit 18e9b5071dbb011d84ade3662d834d8ddd6f8122 Author: ccorbo Date: Wed Feb 21 01:32:26 2024 -0500 feat: add dsa test [PB-2423] (#3510) Co-authored-by: Chris Corbo commit 21bf61171c2fa6529ac2a326b5c0043fcdde313b Author: Dmitry Savintsev Date: Wed Feb 21 05:54:33 2024 +0100 Fix go vet 'composite literals with unkeyed fields' (#3522) commit a06a2ebb13cae1e45dba87db057f0ebb73f1864a Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue Feb 20 15:22:18 2024 -0500 DSA: Remove bids missing DSA object when required (#3487) commit a4f5c119a87e706edd8d8ff3fce368d226973a24 Author: Dmitry Savintsev Date: Tue Feb 20 18:45:21 2024 +0100 Fix go vet composite literals with unkeyed fields (#3507) commit 8e5a7857e2397eb2e8a73820af605301adb7fac8 Author: bold-win <157734532+bold-win@users.noreply.github.com> Date: Mon Feb 19 09:54:00 2024 +0100 New adapter: BoldwinX (#3430) Co-authored-by: boldwin commit cff2fe11da00d0367eca8f87fc6abb3cd31aea23 Author: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Fri Feb 16 06:25:17 2024 -0800 Targeting: Add alwaysincludedeals flag (#3454) commit 34cfa9487d7316ae1168df89f1246997d7174cca Author: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Thu Feb 15 13:16:41 2024 -0800 Remove extra buffer from GZIP compression (#3494) commit c35f67b20818711bd6f6e3b67981a011370fbcb4 Author: Onkar Hanumante Date: Thu Feb 15 21:22:31 2024 +0530 Fix: Release workflow permissions (#3506) commit 902d26234aab2d450627dbbc43e01edaf030b46e Author: Onkar Hanumante Date: Thu Feb 15 12:02:54 2024 +0530 Add log lines in release workflow (#3504) commit e37154baae4a2c3ffe53ad08a7644c908d4ce3ad Author: Lion Pierau Date: Wed Feb 14 21:53:39 2024 +0100 Yieldlab: Add Digital Service Act (DSA) handling (#3473) commit 2b10083e80e5945df31a37ec06d58d8acf9a6f28 Author: Dmitry Savintsev Date: Wed Feb 14 19:56:13 2024 +0100 Fix function godoc comment (#3502) commit 9110a6a2286c3c5d16588c0017798094315b7998 Author: Onkar Hanumante Date: Wed Feb 14 18:56:44 2024 +0530 Update trivy check workflow to use node 20 actions (#3472) commit b3c75875ef3691be5588308048263e35d993a11a Author: Jeremy Sadwith Date: Wed Feb 14 05:51:16 2024 -0500 Kargo Bidder-Info: Adding GPP macros (#3490) commit 97021b43588b70645c593d1db821569d1259730d Author: Onkar Hanumante Date: Wed Feb 14 11:28:03 2024 +0530 Update release workflow to use actions with node 20 support (#3478) commit 8826556a98e8bcb762b9c1eeb8b30542b697e712 Author: Onkar Hanumante Date: Wed Feb 14 10:19:09 2024 +0530 Update code semgrep workflow to use actions with node 20 support (#3471) commit 27c99f963ff2696715ca96b3bcafb4e7847f928e Author: rajatgoyal2510 Date: Tue Feb 13 21:08:52 2024 +0530 Medianet: enable gzip and update usersync url (#3489) Co-authored-by: Aman Jain commit 2da77b0ecb697c42b63be937826ce0647623e332 Author: Onkar Hanumante Date: Tue Feb 13 21:03:11 2024 +0530 Update issue tracking workflow to use actions with node version 20 support (#3479) commit 61a0fac155b936cefcfbc24b97b69461bfbb8dae Author: Onkar Hanumante Date: Tue Feb 13 20:55:56 2024 +0530 Update validate pull request workflows to use node 20 actions (#3474) commit 2848f6b9c150a9884c1d78d993f9c57009e1d649 Author: Mohammad Nurul Islam Shihan <93646635+ishihanvcs@users.noreply.github.com> Date: Tue Feb 13 16:23:51 2024 +0600 ImproveDigital: Bad-Input Error (#3469) commit cb3fd3cf309bdd8282ae5f5d9b05516811f60ce5 Author: Onkar Hanumante Date: Mon Feb 12 23:56:33 2024 +0530 Update code coverage workflow to use node 20 actions (#3470) commit 969313298bb4e644f672c5f9007ccb6245520e5a Author: ym-prasanth <80693980+ym-prasanth@users.noreply.github.com> Date: Thu Feb 8 01:50:38 2024 -0500 Add gpp support for user sync (#3442) authored by: @ym-prasanth commit 535d1f7b543312230229385678555703d2658851 Author: Adprime <64427228+Adprime@users.noreply.github.com> Date: Wed Feb 7 07:48:58 2024 +0200 Adprime: Add mtype (#3439) Co-authored-by: Aiholkin commit 86bddad11be00c8537d71b0a6e975cacd0b081d8 Author: AdView Date: Tue Feb 6 13:42:30 2024 +0800 AdView: support multi imps request & formattype tag for bid response (#3355) authored by: @AdviewOpen commit 3e5918d626b40c90da1937ceeeb235a3e99ddd8e Author: Wls-demo <67785512+Wls-demo@users.noreply.github.com> Date: Mon Feb 5 08:40:55 2024 +0200 Boldwin: get bid type from bid.mtype (#3433) commit 48e5e4d2199c133f6925d6be87f0a5c922b69301 Author: ashishshinde-pubm <109787960+ashishshinde-pubm@users.noreply.github.com> Date: Thu Feb 1 23:36:46 2024 +0530 Capture SeatNonBids for rejected creatives due to insecurity and invalid size (#3376) commit 929ba616c7ee35b834df42c66cde1a893f5ced34 Author: Scott Kay Date: Tue Jan 30 14:55:38 2024 -0500 Update Go-GPP Library (#3425) commit 34046526e75dc9f397e49e572ef5ab39519f098f Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue Jan 30 09:20:07 2024 -0500 Fix: Update Adelement OpenRTB lib to v20 (#3436) commit ab87692921c3d60220364f083c87d99540701ac7 Author: allar15 <60257866+allar15@users.noreply.github.com> Date: Tue Jan 30 15:46:25 2024 +0300 Nextmillennium: add video support & fix server object (#3417) commit 723d2d07b29eb46d577220566bb97a1da0c33808 Author: radadiapg <77797977+radadiapg@users.noreply.github.com> Date: Tue Jan 30 18:01:54 2024 +0530 New adapter: Adelement (#3420) commit dcc2c37b04b2bfe4339cf8ecbaf87e1beb522e6b Author: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Mon Jan 29 13:37:49 2024 -0800 Add $0 Bid Price Warning only if in Debug Mode (#3397) commit 728bc13f8a508d744338c653f3fab6fa2db0133b Author: Scott Kay Date: Mon Jan 29 15:10:20 2024 -0500 Fix OpenRTB 202309 Merge Conflict (#3434) commit dd07431ef54c38a2617c04e76b3e7bda9c21f569 Author: Scott Kay Date: Mon Jan 29 14:21:20 2024 -0500 Update to OpenRTB 2.6-202309 (#3387) commit 70875811a9203426f3f2ab900277a00c44f8fad4 Author: Jaydeep Mohite <30924180+pm-jaydeep-mohite@users.noreply.github.com> Date: Mon Jan 29 23:47:05 2024 +0530 Floors: Add FetchRate to select source of floors from request or dynamically fetched (#3380) commit 0c2865730d948b3d14f2527f0c32820c5935aafa Author: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Thu Jan 25 12:55:36 2024 -0800 Optimize GZIP Compression (#3411) commit 73b895ecb76eca052a71cbcb341d4bef5b4d80c0 Author: Onkar Hanumante Date: Fri Jan 26 00:06:47 2024 +0530 Skip coverage tests if more than two adapters were modified (#3423) Co-authored-by: onkar hanumante commit 8bd778cd00dd7051342b4056971fb250b2fdfc18 Author: Veronika Solovei Date: Thu Jan 25 07:53:13 2024 -0800 Use TransmitUserFPD and TransmitPreciseGeo activities in analytics (#3331) commit 106f22d6152f4bebaebdc6aeeed8572bf1a91f97 Author: Viktor Chernodub <37013688+chernodub@users.noreply.github.com> Date: Thu Jan 25 16:56:34 2024 +0300 Yandex: add adapter (#3419) commit 1ac3fbe456d097f260323cb1036da2fc74797a82 Author: Piotr Jaworski <109736938+piotrj-rtbh@users.noreply.github.com> Date: Thu Jan 25 07:23:14 2024 +0100 RTBHouse: app support (#3422) commit 498955bcd5c242866a9b05886ac4e703cc850b2f Author: Veronika Solovei Date: Tue Jan 23 13:30:58 2024 -0800 Fix race condition in modularity test (#3421) commit ed5e887bec59ffe90c31bd81bc9cd2a019a38572 Author: linux019 Date: Tue Jan 23 21:00:57 2024 +0200 Remove MarshalJSON on BidderName (#3394) Co-authored-by: oaleksieiev commit 446667bf519fa017846180afafc25003203002b5 Author: kalidas-alkimi <92875788+kalidas-alkimi@users.noreply.github.com> Date: Tue Jan 23 18:48:05 2024 +0000 Alkimi: Update user sync URL from dev to prod (#3414) commit eb060ace20e198b0524aed9227f8c085dfffde4a Author: aishwaryapatil Date: Tue Jan 23 13:35:52 2024 -0500 yahooAds: Update maintainer email address (#3410) Co-authored-by: “apatil05” <“aishwarya.patil@yahooinc.com”> commit 896c16544c31734e839eb8103a0055b56024bf68 Author: Scott Kay Date: Tue Jan 23 13:26:04 2024 -0500 Update ReadMe with docker build platform option (#3412) commit bff383b14357317f244c4d5dcf2cb7cbb7f66477 Author: Jeremy Sadwith Date: Tue Jan 23 01:47:33 2024 -0500 Kargo: Update bidder-info (#3404) commit ae291f907a9cf0742934b11472731d3e7e9fd58f Author: Andrea Tumbarello Date: Tue Jan 23 07:44:49 2024 +0100 AIDEM: Added use to macros library (#3403) Co-authored-by: Giovanni Sollazzo Co-authored-by: AndreaC <67786179+darkstarac@users.noreply.github.com> Co-authored-by: darkstar Co-authored-by: AndreaT commit 2ae62cf29c122bcc152b5bdab07817041a4b7e23 Author: Pubmatic-Supriya-Patil <131644110+Pubmatic-Supriya-Patil@users.noreply.github.com> Date: Tue Jan 23 03:35:10 2024 +0530 Fix: Enable deals for soft aliases (#3391) commit daebdcc43389a8b357551b20427e4d50fbe00a00 Author: Zdravko Kosanović <41286499+zkosanovic@users.noreply.github.com> Date: Mon Jan 22 14:45:17 2024 +0400 New adapter: MinuteMedia (#3399) commit 18aa5382a162a4d39eeed3d27deb999283ca9cad Author: Denis Logachov Date: Mon Jan 22 08:26:45 2024 +0200 Adkernel: Add multiformat imp splitting (#3390) authored by @ckbo3hrk commit b806a53b4b8045b106004ac7d86700368c619582 Author: 李向军 <401154047@qq.com> Date: Mon Jan 22 13:11:27 2024 +0800 New Adapter: zMaticoo (#3349) Co-authored-by: adam commit c09ad1acddd9c0cbbf367bcbd32644015b805ab5 Author: Denis Logachov Date: Thu Jan 18 07:27:48 2024 +0200 Unsecure endpoint (#3398) commit 7b36ec668a03302b8ff17a6f2ded7ab9f5e34b45 Author: kalidas-alkimi <92875788+kalidas-alkimi@users.noreply.github.com> Date: Thu Jan 18 05:27:11 2024 +0000 Alkimi: Added User sync URL (#3407) commit 6195581a83392ceb4787ad9e05e96d6430477429 Author: BizzClick <73241175+BizzClick@users.noreply.github.com> Date: Thu Jan 18 07:26:37 2024 +0200 Bizzclick: adapter update, add new host param (#3347) commit fda9f06100a6674c390f22f8a59d9558c135c1f1 Author: Scott Kay Date: Wed Jan 17 18:41:14 2024 -0500 Readme Update (#3246) commit 27b2ff663be24a71c7e9e6f79045272a2e78f1f0 Author: Veronika Solovei Date: Tue Jan 16 11:50:12 2024 -0800 Added TransmitPreciseGeo activity handling in modules (#3348) commit 3789cf91b8e9906d8716000b68817d51971d1559 Author: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Thu Jan 11 22:06:27 2024 -0800 Allow Bidder to Override Callback Type in /setuid (#3301) commit 03314e14fc36db4544276ceb2048bc44ba55d5aa Author: bretg Date: Thu Jan 11 02:06:01 2024 -0500 Update contact info for iqzone (#3360) authored by @bretg commit 967b88241f930eb6f1fdf3d64ce35c200c30feed Author: bretg Date: Thu Jan 11 02:05:25 2024 -0500 Update contact info for iqx (#3365) authored by: @minaguib commit f347436dc33023e08914a51dc28331f75025c908 Author: Maxime Liege <56251840+github-maxime-liege@users.noreply.github.com> Date: Thu Jan 11 08:04:45 2024 +0100 Change email support for Teads adapter (#3386) authored by: @github-maxime-liege commit c49e8eac6289f27db32bb23a7f9803bd46904009 Author: bretg Date: Mon Jan 8 16:37:24 2024 -0500 Bidder geo scope in-line documentation (#3311) commit 0f89ed6dd370f42e7a72aa584f4b48a26789c877 Author: svamiftah <125688973+svamiftah@users.noreply.github.com> Date: Thu Jan 4 11:15:48 2024 +0000 New Adapter: SovrnXSP (#3312) commit 98b0f349dba1d77b8b8d38e1366deca4a106d942 Author: Eugene <73837230+orlov-e@users.noreply.github.com> Date: Thu Jan 4 13:11:23 2024 +0200 smartyads: added vendor id to spec (#3383) commit bd483bb8f838593566463bb1a418cbe75df23d05 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed Jan 3 13:41:32 2024 -0500 Bump golang.org/x/crypto from 0.14.0 to 0.17.0 (#3361) commit 2e7f73a566622ad1cea970100e348029d838866d Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed Jan 3 13:29:38 2024 -0500 Fix: increase tmax in flakey modularity test (#3370) commit 71f20302f7436cd2c707b58f0cc237c2f8d56677 Author: Bryan DeLong Date: Wed Dec 27 06:29:21 2023 -0500 Consumable: App video and audio support (#3338) commit c773b0f1a0c282d12ba46a0b6be2a98be107cd3e Author: JacobKlein26 <42449375+JacobKlein26@users.noreply.github.com> Date: Thu Dec 21 08:58:24 2023 -0500 Nextmillennium: add extra_info support (#3351) commit 0c444d0408cddc6e6a11156f9e60ca9cfdb5ad23 Author: bretg Date: Wed Dec 20 07:40:48 2023 -0500 Rise allows vast modification (#3369) commit 37f0f46de34c9b52b1a8e61cd8ecb0569bb09d2f Author: kmdevops <126434358+kmdevops@users.noreply.github.com> Date: Tue Dec 19 19:25:28 2023 +0100 DXKulture: Change domain (#3345) commit b0daa14b98bbb6bb602c4343ad1fa6f01ec581d8 Author: JuanM <96113351+juanmartinengo@users.noreply.github.com> Date: Mon Dec 18 07:56:00 2023 +0100 Stroeercore: Support video (#3314) Co-authored-by: juanm Co-authored-by: docech commit 91a495f961ebc5066ae175539a4f99a8ead9a309 Author: bretg Date: Mon Dec 18 01:55:18 2023 -0500 Rubicon adapter: enable endpoint compression (#3346) commit 869a3e599253ef181f8eca99c6cc7eef51b91436 Author: Anton Babak <76536883+AntoxaAntoxic@users.noreply.github.com> Date: Mon Dec 18 07:54:48 2023 +0100 Add Magnite Alias for Rubicon Bidder (#3354) commit ec873dcc87a93c380733c5cd96a75d1b12f920d8 Author: Veronika Solovei Date: Mon Dec 11 13:38:33 2023 -0800 Modules activities (#3057) commit 96445436a008457bed0e919557201304726b57df Author: Hendrick Musche <107099114+sag-henmus@users.noreply.github.com> Date: Thu Dec 7 13:12:37 2023 +0100 seedingAlliance: make suntContent alias, add finative alias and add seatId bidder param in seedingAlliance (#3309) co-authored by @sag-henmus commit 8128b24579aa5cebf1f4e99f1054c04201e28b69 Author: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Tue Dec 5 13:29:58 2023 -0500 Add Flag for Host to Enabled/Disable Bidder from Usersync (#3285) commit 3f42b892c0b81eca1501b7712729b0de2ed4150c Author: Vungle-GordonTian <115982294+Vungle-GordonTian@users.noreply.github.com> Date: Tue Dec 5 22:53:22 2023 +0800 Liftoff: Add site support and fill seat (#3327) co-authored by @Vungle-GordonTian commit 005a2d176c0e133a52384c21ca6c026d8bc03644 Author: Hugo Lindström Date: Tue Dec 5 15:50:43 2023 +0100 New Adapter: Relevant Digital (#3216) co-authored by: @hugolm84 commit db74611cb6148b63ba106bbe519b90392e756afd Author: Piotr Jaworski <109736938+piotrj-rtbh@users.noreply.github.com> Date: Tue Dec 5 07:28:55 2023 +0100 RTBHouse: native support (renewal) (#3328) commit c7f3e2298d9236fbff6ad79cf888ffe78c1ade6a Author: Alexander Pykhteyev Date: Tue Dec 5 13:18:20 2023 +0700 Add Embi media adapter (#3325) Co-authored-by: apykhteyev commit 1fe320f0d29dd01fb46a71f5abaecd754f204b24 Author: Ahmet Faruk Karakus <65093478+ahmetfaruk59@users.noreply.github.com> Date: Tue Dec 5 09:17:37 2023 +0300 HuaweiAds: add popular sizes for video native (#3324) commit 4de96b2061774465107a6785f7f4e05a4ce06c1a Author: ashishshinde-pubm <109787960+ashishshinde-pubm@users.noreply.github.com> Date: Tue Dec 5 00:10:46 2023 +0530 Fix: single bid processing from alternatebiddercode (#3313) commit e2da4460646849c02a89c76325b96ec284969bf5 Author: ashishshinde-pubm <109787960+ashishshinde-pubm@users.noreply.github.com> Date: Fri Dec 1 23:27:38 2023 +0530 Fix: setuid endpoint sets cookie when both GDPR and GPP are set (#3165) commit a6dea2f26d0dcf9e20c30de8297dad8931a319ee Author: ym-prasanth <80693980+ym-prasanth@users.noreply.github.com> Date: Thu Nov 30 05:47:42 2023 -0500 Yieldmo: Get bid type from bid.ext.mediatype in bid response (#3295) commit b7d474ff6b0b34c4c18cfa43c5a104d4a134ee16 Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue Nov 28 11:42:12 2023 -0500 Fix: Adhese handle 200 response with empty array body (#3323) commit d82c99733d7f0a41e5f15af95c8a5e45ab60177c Author: Wls-demo <67785512+Wls-demo@users.noreply.github.com> Date: Tue Nov 28 11:20:51 2023 +0200 Boldwin: add user sync url (#3316) commit df072ff78891c49a2e011dcb7dcb2f5f0d9c6d7f Author: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Mon Nov 27 17:19:58 2023 -0500 Allow bidders to skip sync for GDPR/GPP (#3265) commit 020a7ddd8a7597582b4fd87fd56afa27a5013df6 Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Mon Nov 27 16:48:38 2023 -0500 Fix: refactor flakey category dedupe test (#3310) commit 9cf8accf4350f9149a88789efcb9df406fb55e3c Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Mon Nov 27 16:42:58 2023 -0500 Fix: flakey deal tier test (#3322) commit fb5e8492da15733e9035adfc54029bcb9494944e Author: Sonali-More-Xandr <87759626+Sonali-More-Xandr@users.noreply.github.com> Date: Mon Nov 27 18:19:49 2023 +0530 Migrate freewheelsspold alias of frewheelssp to use new pattern (#3204) commit 7558fde3a48ea520308edcd68c77f62a1af20416 Author: Sonali-More-Xandr <87759626+Sonali-More-Xandr@users.noreply.github.com> Date: Mon Nov 27 18:18:36 2023 +0530 Migrate trustx alias of grid to use new pattern (#3206) commit eb79f41e6574e6f922876ae04f657dc60e2ff3f9 Author: Sonali-More-Xandr <87759626+Sonali-More-Xandr@users.noreply.github.com> Date: Mon Nov 27 12:26:32 2023 +0530 Migrate yahoossp alias of yahooAds to use new pattern (#3208) * Migrate yahoossp alias of yahooAds to use new pattern Co-authored-by: Scott Kay commit 4cc2faacfcef931da1d3ad8233a23d2e4b255ffc Author: ccorbo Date: Thu Nov 23 02:10:18 2023 -0500 IX: add privacy sandbox support (#3252) Co-authored-by: Chris Corbo commit 6f5b03e03fc9e62cd86f093b6650e823853a3217 Author: Ahmet Faruk Karakus <65093478+ahmetfaruk59@users.noreply.github.com> Date: Thu Nov 23 09:56:37 2023 +0300 Huaweiads: Remove empty image assets from response (#3308) commit 54039e9288d7697a10b2ecaccff88419c42881c3 Author: Gena Date: Thu Nov 23 08:19:30 2023 +0200 Add indicue adapter (#3291) commit 8419142b7f7cd7157878f4c1397a67ede789e923 Author: Louis Billaut Date: Thu Nov 23 07:05:12 2023 +0100 bliink: iframe added to usersync (#3243) Co-authored-by: louisbillaut Co-authored-by: samous Co-authored-by: nowfeel commit 686921136188dfda55b357455cc2b04822ae0fe9 Author: guscarreon Date: Mon Nov 20 13:13:43 2023 -0500 Adapter Name Case Insensitive: dealTier (#3218) commit e5c920a3d8d4fe80c698a02f6cda245065d01b20 Author: Nilesh Chate <97721111+pm-nilesh-chate@users.noreply.github.com> Date: Mon Nov 20 22:50:40 2023 +0530 Pass currency converter to modules (#3278) commit 06e1145971c81c66c884a77b1fe2cf0752d1d2f0 Author: kalidas-alkimi <92875788+kalidas-alkimi@users.noreply.github.com> Date: Mon Nov 20 12:38:58 2023 +0000 New Adapter: Alkimi (#3247) co-authored by @kalidas-alkimi @pro-nsk commit 1e89053b4780a81fe99242daf153875d4e509fbb Author: Maxime Liege <56251840+github-maxime-liege@users.noreply.github.com> Date: Mon Nov 20 10:02:09 2023 +0100 Change endpoint for Teads adapter (#3303) commit ea01ccff4100235f8af425011785ad7adfdb4a9d Author: Vungle-GordonTian <115982294+Vungle-GordonTian@users.noreply.github.com> Date: Mon Nov 20 17:01:24 2023 +0800 update contact email address (#3298) commit 94d86825acf52c25ea9ca648d9a4fc5b09e72c03 Author: Witek Galecki <49022890+wgalecki@users.noreply.github.com> Date: Mon Nov 20 09:07:54 2023 +0100 adquery: added device ip and ua to bidder request (#1) (#3288) commit 01f3320e4ea1a6c794418817ae80ad9d107835a6 Author: Simple Magic Software Date: Mon Nov 20 02:30:36 2023 -0500 New Adapter: oms (#3264) Co-authored-by: yfcr commit cad9097fd5731c5042a13747eb5220152e6a7a5d Author: Hasan Kanjee <55110940+hasan-kanjee@users.noreply.github.com> Date: Mon Nov 20 01:27:55 2023 -0500 Flipp: abide by privacy concerns when using flippExt userKey (#3250) commit 515b9372f850f856c8c2f8875c624aa9b9816512 Author: Ahmet Faruk Karakus <65093478+ahmetfaruk59@users.noreply.github.com> Date: Mon Nov 20 09:06:20 2023 +0300 HuaweiAds: Increase fill rate for native ads (#3279) co-authored by @ahmetfaruk59 commit f5f1e072180de52d1f17d9f51cc5e7c38785ca99 Author: BrightMountainMedia <69471268+BrightMountainMediaInc@users.noreply.github.com> Date: Sat Nov 18 03:45:53 2023 +0530 BMTM: app support (#3280) commit 7e06aae947489da90eb3020e4cd36a62bb9485ca Author: Sonali-More-Xandr <87759626+Sonali-More-Xandr@users.noreply.github.com> Date: Sat Nov 18 03:37:05 2023 +0530 Migrate epsilon alias of conversant to use new pattern (#3207) commit 21ee27b948dc501184ea666847de14cbeb1e9de3 Author: Sonali-More-Xandr <87759626+Sonali-More-Xandr@users.noreply.github.com> Date: Sat Nov 18 02:49:54 2023 +0530 Migrate synacormedia alias of imds to use new pattern (#3205) commit 738884f7cc4182f712d679df800f13e847f1ccac Author: Sonali-More-Xandr <87759626+Sonali-More-Xandr@users.noreply.github.com> Date: Sat Nov 18 02:36:36 2023 +0530 Migrate yahooAdvertising alias of yahooAds to use new pattern (#3203) commit 9693797b3d0eb81c71e86c967b5604d202b9a269 Author: gg-natalia <148577437+gg-natalia@users.noreply.github.com> Date: Thu Nov 16 15:59:47 2023 -0300 GumGum: Add product parameter (#3253) commit 832fbf2d7c3f207d0e86807536045369b79b30d1 Author: Nilesh Chate <97721111+pm-nilesh-chate@users.noreply.github.com> Date: Fri Nov 17 00:23:11 2023 +0530 PubMatic: Support hardcoded alias (#3224) commit 79d2f27870c928c410c8159947a596a6bd2550e6 Author: abermanov-zeta <95416296+abermanov-zeta@users.noreply.github.com> Date: Thu Nov 16 19:24:06 2023 +0100 Zeta Global SSP: Update endpoints (#3201) commit 6a20c0e1945df1a46690a26aaf79183b321318fb Author: Veronika Solovei Date: Thu Nov 16 10:16:36 2023 -0800 Privacy scrubber refactoring (#3108) commit 905436f335c9cbb20a3a827adb64a87aa6a25627 Author: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Thu Nov 16 09:59:53 2023 -0800 Add debug flag to cookie sync endpoint (#3107) commit b904b530658aefa13739d6afd5acb38c04c09ed4 Author: ccorbo Date: Thu Nov 16 12:47:54 2023 -0500 fix: pass through bid.prebid.meta, currently it is being dropped (#3100) Co-authored-by: Chris Corbo commit f9f5546542aa76672156d9220100e18dfc6996e0 Author: Veronika Solovei Date: Thu Nov 16 09:20:00 2023 -0800 Modularity: Make request wrapper accessible in bidder request hook (#3096) commit fb94284028447674c65eef901cb7d42665d4e521 Author: Nikhil Vaidya <102963966+pm-nikhil-vaidya@users.noreply.github.com> Date: Thu Nov 16 22:27:01 2023 +0530 Floors: Dynamic fetch (#2732) commit ee0869d21d31a7a2891e31e3c2b8bf75e08e600e Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu Nov 16 11:34:24 2023 -0500 Bump google.golang.org/grpc from 1.53.0 to 1.56.3 (#3258) commit ea04a390902c9a793f3667827b0e1912d25af13e Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu Nov 16 10:02:41 2023 -0500 Bump golang.org/x/net from 0.7.0 to 0.17.0 (#3202) commit 180eb410cb9f6e1751621797171cec7fa2096122 Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed Nov 15 13:47:22 2023 -0500 Fix: Revert JSON lib used to prepare /bidders/params response (#3300) commit f52dbb192288d677044733f1f12b0e9c12e9ed50 Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Mon Nov 13 23:51:25 2023 -0500 Fix: use bidder info fs config as base when merging overrides (#3289) commit 909926477ee48c0f2dffbbe2663649e4c586c75c Author: Veronika Solovei Date: Mon Nov 13 10:24:34 2023 -0800 Fix for null string unmarshal error (#3284) commit 5941d46efe3a59015750e56d293838b44c309701 Author: ashishshinde-pubm <109787960+ashishshinde-pubm@users.noreply.github.com> Date: Mon Nov 13 23:36:39 2023 +0530 Fix: BidderInfo OpenRTB field data lost on start up (#3178) commit b200357f116b7c5ece866bf21f5c62e002787b0a Author: guscarreon Date: Tue Oct 31 11:57:02 2023 -0400 Increment to V2 in Dockerfile and Makefile (#3272) commit 72463be04b6e9a5b969839024f43899fd8be533f Author: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Mon Oct 30 15:21:56 2023 -0400 Revert "RTBHouse: native support (#3212)" (#3271) This reverts commit 2540dd884950f6c582d01263479f3e7973cda2eb. commit 3834c22f6c39035d529aede3a059fcff78489c38 Author: Sonali-More-Xandr <87759626+Sonali-More-Xandr@users.noreply.github.com> Date: Mon Oct 30 23:58:14 2023 +0530 Revert "Rubicon: Add `imp[].ext.rp.rtb.formats` logic (#3255)" (#3268) This reverts commit 8e9f3f359416289a8b988f5d2403daf68edc4593. commit 8e9f3f359416289a8b988f5d2403daf68edc4593 Author: SerhiiNahornyi Date: Mon Oct 30 14:19:46 2023 +0200 Rubicon: Add `imp[].ext.rp.rtb.formats` logic (#3255) commit 2540dd884950f6c582d01263479f3e7973cda2eb Author: Piotr Jaworski <109736938+piotrj-rtbh@users.noreply.github.com> Date: Mon Oct 30 09:00:24 2023 +0100 RTBHouse: native support (#3212) commit ec729e6e5d50a1306d76332fda52108b9d313c1d Author: Scott Kay Date: Fri Oct 20 16:22:32 2023 -0400 Increment Package Version To V2 (#3245) commit 6b98a812c2d8adf69e9ff45eaeb108f8097231f3 Author: guscarreon Date: Fri Oct 20 14:26:41 2023 -0400 Adapter Name Case Insensitive: alternate bidder codes (#3229) commit 53e0adcaf0af3203fd4e235d1f4fe4a4cce80a79 Author: Veronika Solovei Date: Fri Oct 20 08:24:10 2023 -0700 Adapter Name Case Insensitive: Stored Bid Responses (#3197) --- .devcontainer/devcontainer.json | 2 +- .github/workflows/adapter-code-coverage.yml | 36 +- .../workflows/helpers/pull-request-utils.js | 27 +- .github/workflows/issue_prioritization.yml | 2 +- .github/workflows/release.yml | 25 +- .github/workflows/security.yml | 4 +- .github/workflows/semgrep.yml | 23 +- .github/workflows/validate-merge.yml | 6 +- .github/workflows/validate.yml | 6 +- .gitignore | 12 +- Dockerfile | 18 +- Makefile | 8 +- README.md | 124 +- account/account.go | 25 +- account/account_test.go | 54 +- adapters/33across/33across.go | 30 +- adapters/33across/33across_test.go | 6 +- .../exemplary/bidresponse-defaults.json | 3 +- .../exemplary/instream-video-defaults.json | 3 +- .../33acrosstest/exemplary/multi-format.json | 3 +- .../exemplary/multi-imp-banner.json | 9 +- .../exemplary/optional-params.json | 3 +- .../exemplary/outstream-video-defaults.json | 3 +- .../33acrosstest/exemplary/site-banner.json | 3 +- .../33acrosstest/exemplary/site-video.json | 3 +- .../multi-imp-mixed-validation.json | 3 +- .../supplemental/status-not-ok.json | 3 +- .../video-validation-fail-size-null.json | 29 + .../video-validation-fail-size-partial.json | 30 + .../video-validation-fail-size-zero.json | 31 + adapters/33across/params_test.go | 2 +- adapters/aax/aax.go | 16 +- adapters/aax/aax_test.go | 6 +- .../aax/aaxtest/exemplary/multi-format.json | 3 +- .../aax/aaxtest/exemplary/multi-imps.json | 3 +- adapters/aax/aaxtest/exemplary/no-bid.json | 3 +- .../aaxtest/exemplary/optional-params.json | 3 +- .../aax/aaxtest/exemplary/simple-banner.json | 3 +- .../aax/aaxtest/exemplary/simple-video.json | 3 +- ...valid-req-400-status-code-bad-request.json | 3 +- .../invalid-resp-diff-imp-id.json | 3 +- .../invalid-resp-multi-imp-type.json | 3 +- .../valid-req-200-bid-response-from-aax.json | 3 +- .../valid-req-204-response-from-aax.json | 3 +- adapters/aax/params_test.go | 2 +- adapters/aceex/aceex.go | 20 +- adapters/aceex/aceex_test.go | 6 +- .../aceex/aceextest/exemplary/banner-app.json | 3 +- .../aceex/aceextest/exemplary/banner-web.json | 3 +- .../aceex/aceextest/exemplary/native-app.json | 3 +- .../aceex/aceextest/exemplary/native-web.json | 3 +- .../aceex/aceextest/exemplary/video-app.json | 3 +- .../aceex/aceextest/exemplary/video-web.json | 3 +- .../supplemental/empty-seatbid-array.json | 3 +- .../supplemental/invalid-response.json | 3 +- .../supplemental/status-code-bad-request.json | 3 +- .../supplemental/status-code-no-content.json | 3 +- .../supplemental/status-code-other-error.json | 3 +- .../status-code-service-unavailable.json | 3 +- adapters/aceex/params_test.go | 2 +- adapters/acuityads/acuityads.go | 20 +- adapters/acuityads/acuityads_test.go | 6 +- .../acuityadstest/exemplary/banner-app.json | 3 +- .../acuityadstest/exemplary/banner-web.json | 3 +- .../acuityadstest/exemplary/native-app.json | 3 +- .../acuityadstest/exemplary/native-web.json | 3 +- .../acuityadstest/exemplary/video-app.json | 3 +- .../acuityadstest/exemplary/video-web.json | 3 +- .../supplemental/empty-seatbid-array.json | 3 +- .../supplemental/invalid-response.json | 3 +- .../supplemental/status-code-bad-request.json | 3 +- .../supplemental/status-code-no-content.json | 3 +- .../supplemental/status-code-other-error.json | 3 +- .../status-code-service-unavailable.json | 3 +- adapters/acuityads/params_test.go | 2 +- adapters/adapterstest/adapter_test_util.go | 2 +- adapters/adapterstest/test_json.go | 44 +- adapters/adelement/adelement.go | 141 + adapters/adelement/adelement_test.go | 28 + .../adelementtest/exemplary/audio-app.json | 91 + .../adelementtest/exemplary/audio-web.json | 91 + .../adelementtest/exemplary/banner-app.json | 140 + .../adelementtest/exemplary/banner-web.json | 128 + .../adelementtest}/exemplary/native-app.json | 42 +- .../adelementtest}/exemplary/native-web.json | 42 +- .../adelementtest/exemplary/video-app.json | 150 + .../adelementtest/exemplary/video-web.json | 148 + .../supplemental/empty-seatbid-array.json | 120 + .../invalid-aceex-ext-object.json} | 0 .../supplemental/invalid-response.json | 25 +- .../supplemental/status-code-bad-request.json | 10 +- .../supplemental/status-code-no-content.json | 8 +- .../supplemental/status-code-other-error.json | 10 +- .../status-code-service-unavailable.json | 10 +- adapters/adf/adf.go | 22 +- adapters/adf/adf_test.go | 6 +- .../adf/adftest/exemplary/dynamic-tag.json | 3 +- .../adf/adftest/exemplary/multi-format.json | 3 +- .../adf/adftest/exemplary/multi-native.json | 3 +- ...gle-banner-pricetype-gross-extend-ext.json | 3 +- .../single-banner-pricetype-gross.json | 3 +- .../single-banner-pricetype-net.json | 3 +- .../adf/adftest/exemplary/single-banner.json | 3 +- .../adf/adftest/exemplary/single-native.json | 3 +- .../adf/adftest/exemplary/single-video.json | 3 +- ...nners-different-pricetypes-extend-ext.json | 3 +- .../two-banners-different-pricetypes.json | 3 +- .../adf/adftest/supplemental/bad-request.json | 3 +- .../adftest/supplemental/empty-response.json | 3 +- .../supplemental/invalid-imp-mediatype.json | 3 +- .../adftest/supplemental/nobid-response.json | 3 +- .../adftest/supplemental/server-error.json | 3 +- .../supplemental/unparsable-response.json | 5 +- adapters/adf/params_test.go | 2 +- adapters/adgeneration/adgeneration.go | 19 +- adapters/adgeneration/adgeneration_test.go | 11 +- .../exemplary/single-banner-android.json | 3 +- .../exemplary/single-banner-ios.json | 3 +- .../exemplary/single-banner.json | 3 +- .../supplemental/204-bid-response.json | 3 +- .../supplemental/400-bid-response.json | 3 +- .../supplemental/no-bid-response.json | 3 +- adapters/adgeneration/params_test.go | 2 +- adapters/adhese/adhese.go | 31 +- adapters/adhese/adhese_test.go | 6 +- .../adhesetest/exemplary/banner-internal.json | 3 +- .../adhesetest/exemplary/banner-market.json | 3 +- .../exemplary/banner-video-internal.json | 3 +- .../adhese/adhesetest/exemplary/video.json | 4 +- .../req-invalid-empty-imp-ext.json | 4 +- .../supplemental/req-invalid-no-imp-ext.json | 4 +- .../supplemental/res-invalid-height.json | 5 +- .../supplemental/res-invalid-no-body.json | 7 +- .../supplemental/res-invalid-no-origin.json | 3 +- .../supplemental/res-invalid-price.json | 5 +- .../res-invalid-status-not-ok.json | 3 +- .../supplemental/res-invalid-width.json | 5 +- .../supplemental/res-no_bids_200.json | 55 + ...{res-no_bids.json => res-no_bids_204.json} | 3 +- .../res-no_impression_counter.json | 3 +- adapters/adhese/params_test.go | 2 +- adapters/adhese/utils.go | 2 +- adapters/adkernel/adkernel.go | 147 +- adapters/adkernel/adkernel_test.go | 8 +- .../exemplary/multiformat-impression.json | 90 +- .../exemplary/single-banner-impression.json | 9 +- .../exemplary/single-video-impression.json | 9 +- adapters/adkernelAdn/adkernelAdn.go | 24 +- adapters/adkernelAdn/adkernelAdn_test.go | 6 +- .../exemplary/multiformat-impression.json | 3 +- .../exemplary/single-banner-impression.json | 3 +- .../exemplary/single-video-impression.json | 3 +- .../supplemental/204status.json | 3 +- .../supplemental/http-err-status.json | 3 +- .../two-impressions-two-seatbids.json | 3 +- .../supplemental/wrong-imp-ext-1.json | 4 +- adapters/adman/adman.go | 18 +- adapters/adman/adman_test.go | 6 +- .../admantest/exemplary/simple-banner.json | 3 +- .../admantest/exemplary/simple-video.json | 3 +- .../exemplary/simple-web-banner.json | 3 +- .../admantest/supplemental/bad-imp-ext.json | 4 +- .../admantest/supplemental/bad_response.json | 5 +- .../admantest/supplemental/no-imp-ext-1.json | 2 +- .../admantest/supplemental/no-imp-ext-2.json | 4 +- .../admantest/supplemental/status-204.json | 3 +- .../admantest/supplemental/status-404.json | 3 +- adapters/adman/params_test.go | 2 +- adapters/admatic/admatic.go | 139 + adapters/admatic/admatic_test.go | 29 + .../admatic/admatictest/exemplary/banner.json | 96 + .../admatictest/exemplary/multiple-imps.json | 181 + .../admatic/admatictest/exemplary/native.json | 86 + .../exemplary/optional-params.json | 100 + .../admatic/admatictest/exemplary/video.json | 94 + .../admatictest/supplemental/bad-request.json | 66 + .../multiple-imps-with-error.json | 119 + .../response-200-without-body.json | 64 + .../supplemental/response-204.json | 59 + .../supplemental/server-error.json | 65 + adapters/admatic/params_test.go | 56 + adapters/admixer/admixer.go | 18 +- adapters/admixer/admixer_test.go | 6 +- .../exemplary/optional-params.json | 3 +- .../exemplary/simple-app-audio.json | 3 +- .../exemplary/simple-app-banner.json | 3 +- .../exemplary/simple-app-native.json | 3 +- .../exemplary/simple-app-video.json | 3 +- .../exemplary/simple-site-audio.json | 3 +- .../exemplary/simple-site-banner.json | 3 +- .../exemplary/simple-site-native.json | 3 +- .../exemplary/simple-site-video.json | 3 +- .../supplemental/bad-dsp-request-example.json | 3 +- .../dsp-server-internal-error-example.json | 3 +- .../unknown-status-code-example.json | 3 +- adapters/admixer/params_test.go | 2 +- adapters/adnuntius/adnuntius.go | 131 +- adapters/adnuntius/adnuntius_test.go | 8 +- .../exemplary/simple-banner.json | 5 +- .../supplemental/check-dealId.json | 5 +- ...heck-dsa-advertiser-legalName-omitted.json | 133 + .../check-dsa-advertiser-legalName.json | 134 + .../check-dsa-advertiser-omitted.json | 123 + .../supplemental/check-gdpr.json | 5 +- .../supplemental/check-gross-bids.json | 5 +- .../supplemental/check-net-bids.json | 5 +- .../check-noCookies-parameter.json | 5 +- .../supplemental/check-noCookies.json | 5 +- .../supplemental/check-order-multi-imp.json | 9 +- .../supplemental/check-price-type-error.json | 2 +- .../supplemental/check-userId.json | 5 +- .../supplemental/empty-regs-ext.json | 5 +- .../supplemental/empty-regs.json | 5 +- .../supplemental/height-error.json | 7 +- .../supplemental/invalid-regs-ext.json | 46 + .../supplemental/max-deals-test.json | 7 +- .../supplemental/send-header-information.json | 5 +- .../adnuntiustest/supplemental/site-ext.json | 114 + .../supplemental/size-check.json | 5 +- .../supplemental/status-400.json | 5 +- .../supplemental/test-networks.json | 5 +- .../adnuntiustest/supplemental/user-ext.json | 113 + .../supplemental/width-error.json | 7 +- adapters/adnuntius/params_test.go | 2 +- adapters/adocean/adocean.go | 26 +- adapters/adocean/adocean_test.go | 6 +- .../exemplary/multi-banner-impression.json | 3 +- .../exemplary/single-banner-impression.json | 3 +- .../adocean/adoceantest/supplemental/app.json | 5 +- .../supplemental/bad-response.json | 5 +- .../supplemental/encode-error.json | 3 +- .../supplemental/network-error.json | 3 +- .../adoceantest/supplemental/no-bid.json | 6 +- .../adoceantest/supplemental/no-sizes.json | 6 +- .../supplemental/requests-merge.json | 6 +- adapters/adocean/params_test.go | 2 +- adapters/adoppler/adoppler.go | 42 +- adapters/adoppler/adoppler_test.go | 6 +- .../adopplertest/exemplary/custom-client.json | 3 +- .../exemplary/default-client.json | 3 +- .../adopplertest/exemplary/multiimp.json | 9 +- .../supplemental/bad-request.json | 3 +- .../supplemental/duplicate-imp.json | 6 +- .../supplemental/invalid-impid.json | 3 +- .../supplemental/invalid-response.json | 5 +- .../supplemental/invalid-video-ext.json | 8 +- .../adopplertest/supplemental/no-bid.json | 3 +- .../supplemental/server-error.json | 3 +- adapters/adot/adot.go | 20 +- adapters/adot/adot_test.go | 10 +- .../adottest/exemplary/simple-banner.json | 3 +- .../exemplary/simple-interstitial.json | 3 +- .../adottest/exemplary/simple-native.json | 3 +- .../adot/adottest/exemplary/simple-video.json | 3 +- .../supplemental/ext-bidder-empty.json | 3 +- .../ext-bidder-publisher-path.json | 3 +- .../adottest/supplemental/simple-audio.json | 3 +- .../supplemental/simple-parallax.json | 3 +- .../adottest/supplemental/status_204.json | 3 +- .../adottest/supplemental/status_400.json | 3 +- .../adottest/supplemental/status_500.json | 3 +- .../supplemental/unmarshal_error.json | 5 +- adapters/adot/params_test.go | 2 +- adapters/adpone/adpone.go | 18 +- adapters/adpone/adpone_test.go | 6 +- .../adponetest/exemplary/simple-banner.json | 3 +- .../adponetest/supplemental/bad_response.json | 5 +- .../adponetest/supplemental/status_204.json | 3 +- .../adponetest/supplemental/status_400.json | 3 +- .../adponetest/supplemental/status_418.json | 3 +- adapters/adprime/adprime.go | 48 +- adapters/adprime/adprime_test.go | 6 +- .../adprimetest/exemplary/simple-banner.json | 5 +- .../adprimetest/exemplary/simple-native.json | 5 +- .../adprimetest/exemplary/simple-video.json | 5 +- .../exemplary/simple-web-banner.json | 5 +- .../adprimetest/exemplary/withAudiences.json | 5 +- .../adprimetest/exemplary/withKeywords.json | 5 +- .../adprimetest/supplemental/bad-imp-ext.json | 4 +- .../supplemental/bad_media_type.json | 5 +- .../supplemental/bad_response.json | 5 +- .../supplemental/no-imp-ext-1.json | 2 +- .../supplemental/no-imp-ext-2.json | 4 +- .../adprimetest/supplemental/status-204.json | 3 +- .../adprimetest/supplemental/status-400.json | 3 +- .../adprimetest/supplemental/status-404.json | 3 +- adapters/adprime/params_test.go | 2 +- adapters/adquery/adquery.go | 53 +- adapters/adquery/adquery_test.go | 9 +- .../adquery/adquerytest/exemplary/empty.json | 9 +- .../adquerytest/exemplary/many-imps.json | 61 +- .../adquerytest/exemplary/no-currency.json | 40 +- .../adquery/adquerytest/exemplary/ok.json | 41 +- .../exemplary/single-imp-banner-format.json | 32 +- .../adquerytest/supplemental/data-null.json | 18 +- .../invalid-numerical-values.json | 54 +- .../supplemental/malformed-ext.json | 2 +- .../supplemental/malformed-resp.json | 40 +- .../supplemental/mediatype-unknown.json | 40 +- .../supplemental/mediatype-video.json | 40 +- .../adquerytest/supplemental/no-device.json | 129 + .../supplemental/no-imp-banner-measures.json | 18 +- .../supplemental/no-imp-banner.json | 18 +- .../adquerytest/supplemental/no-site.json | 131 + .../supplemental/resp-bad-request.json | 36 +- .../supplemental/resp-no-content.json | 29 +- .../supplemental/resp-server-error.json | 36 +- adapters/adquery/params_test.go | 2 +- adapters/adquery/types.go | 5 +- adapters/adrino/adrino.go | 14 +- adapters/adrino/adrino_test.go | 6 +- .../adrino/adrinotest/exemplary/no-bid.json | 3 +- .../adrinotest/exemplary/single-native.json | 3 +- .../adrinotest/supplemental/unknown-hash.json | 3 +- adapters/adrino/params_test.go | 2 +- adapters/adsinteractive/adsinteractive.go | 14 +- .../adsinteractive/adsinteractive_test.go | 6 +- .../adsinteractivetest/exemplary/banner.json | 3 +- .../supplemental/bad-request-400.json | 3 +- .../supplemental/bad-response.json | 5 +- .../supplemental/no-content-204.json | 3 +- .../supplemental/service-unavailable-503.json | 3 +- adapters/adsinteractive/params_test.go | 2 +- adapters/adtarget/adtarget.go | 32 +- adapters/adtarget/adtarget_test.go | 6 +- .../exemplary/media-type-mapping.json | 3 +- .../adtargettest/exemplary/simple-banner.json | 3 +- .../adtargettest/exemplary/simple-video.json | 3 +- .../supplemental/explicit-dimensions.json | 3 +- .../supplemental/wrong-impression-ext.json | 2 +- .../wrong-impression-mapping.json | 3 +- adapters/adtarget/params_test.go | 14 +- adapters/adtelligent/adtelligent.go | 31 +- adapters/adtelligent/adtelligent_test.go | 6 +- .../exemplary/media-type-mapping.json | 3 +- .../exemplary/simple-banner.json | 3 +- .../exemplary/simple-video.json | 3 +- .../supplemental/explicit-dimensions.json | 3 +- .../supplemental/wrong-impression-ext.json | 2 +- .../wrong-impression-mapping.json | 3 +- adapters/adtelligent/params_test.go | 14 +- adapters/adtonos/adtonos.go | 143 + adapters/adtonos/adtonos_test.go | 30 + .../exemplary/simple-audio-with-mtype.json | 115 + .../adtonostest/exemplary/simple-audio.json | 113 + .../adtonostest/exemplary/simple-video.json | 113 + .../wrong-impression-mapping.json | 103 + adapters/adtonos/params_test.go | 43 + adapters/adtrgtme/adtrgtme.go | 18 +- adapters/adtrgtme/adtrgtme_test.go | 6 +- .../adtrgtmetest/exemplary/banner-app.json | 9 +- .../adtrgtmetest/exemplary/banner-web.json | 9 +- .../supplemental/banner-app-headers-ipv6.json | 3 +- .../supplemental/banner-web-headers-ipv6.json | 3 +- .../supplemental/empty-seatbid-array.json | 3 +- .../supplemental/invalid-response.json | 3 +- .../supplemental/not-found-imp.json | 3 +- .../supplemental/status-code-bad-request.json | 3 +- .../status-code-internal-server-error.json | 3 +- .../supplemental/status-code-no-content.json | 3 +- .../status-code-service-unavaliable.json | 3 +- .../status-code-temporary-redirect.json | 3 +- .../unsupported-bid-type-native.json | 3 +- .../unsupported-bid-type-video.json | 3 +- adapters/adtrgtme/params_test.go | 2 +- adapters/advangelists/advangelists.go | 24 +- adapters/advangelists/advangelists_test.go | 6 +- .../advangeliststest/exemplary/banner.json | 3 +- .../advangeliststest/exemplary/video.json | 3 +- adapters/advangelists/params_test.go | 3 +- adapters/adview/adview.go | 160 +- adapters/adview/adview_test.go | 6 +- .../exemplary/banner-app-format.json | 15 +- .../banner-app-resp-no-formattype.json | 151 + .../adviewtest/exemplary/banner-app.json | 15 +- .../adviewtest/exemplary/native-app.json | 15 +- .../adviewtest/exemplary/video-app.json | 15 +- .../adviewtest/supplemental/bad-request.json | 3 +- .../supplemental/empty-response.json | 3 +- .../supplemental/nobid-response.json | 3 +- .../adviewtest/supplemental/server-error.json | 3 +- .../supplemental/unparsable-response.json | 5 +- adapters/adview/params_test.go | 2 +- adapters/adxcg/adxcg.go | 14 +- adapters/adxcg/adxcg_test.go | 6 +- .../exemplary/simple-banner-currency.json | 3 +- .../adxcgtest/exemplary/simple-banner.json | 3 +- .../adxcgtest/exemplary/simple-native.json | 3 +- .../adxcgtest/exemplary/simple-video.json | 3 +- .../adxcgtest/supplemental/bad_response.json | 5 +- .../adxcgtest/supplemental/status_204.json | 3 +- .../adxcgtest/supplemental/status_400.json | 3 +- .../adxcgtest/supplemental/status_418.json | 3 +- adapters/adyoulike/adyoulike.go | 14 +- adapters/adyoulike/adyoulike_test.go | 6 +- .../exemplary/currency-conversion.json | 3 +- .../exemplary/multiformat-impression.json | 3 +- .../supplemental/invalid-bid-response.json | 5 +- .../supplemental/status-bad-request.json | 3 +- .../supplemental/status-no-content.json | 3 +- .../status-service-unavailable.json | 3 +- .../supplemental/status-unknown.json | 3 +- adapters/adyoulike/params_test.go | 2 +- adapters/aidem/aidem.go | 66 +- adapters/aidem/aidem_test.go | 10 +- .../aidemtest/exemplary/multi-format.json | 5 +- .../exemplary/multi-imps-multi-bid.json | 5 +- .../exemplary/multi-imps-single-bid.json | 5 +- .../aidem/aidemtest/exemplary/no-bid.json | 5 +- .../aidemtest/exemplary/optional-params.json | 5 +- .../aidemtest/exemplary/simple-banner.json | 5 +- .../aidemtest/exemplary/simple-video.json | 5 +- ...valid-req-400-status-code-bad-request.json | 5 +- ...nvalid-res-200-status-code-empty-bids.json | 5 +- .../invalid-resp-multi-imp-type.json | 5 +- ...valid-req-200-bid-response-from-aidem.json | 5 +- .../valid-req-204-response-from-aidem.json | 5 +- adapters/aidem/params_test.go | 2 +- adapters/aja/aja.go | 18 +- adapters/aja/aja_test.go | 6 +- .../exemplary/banner-multiple-imps.json | 6 +- adapters/aja/ajatest/exemplary/video.json | 3 +- .../supplemental/invalid-bid-type.json | 3 +- .../supplemental/invalid-ext-bidder.json | 2 +- .../aja/ajatest/supplemental/invalid-ext.json | 2 +- .../supplemental/status-bad-request.json | 3 +- .../status-internal-server-error.json | 3 +- .../supplemental/status-no-content.json | 3 +- adapters/algorix/algorix.go | 27 +- adapters/algorix/algorix_test.go | 6 +- .../exemplary/sample-banner-euc.json | 3 +- .../exemplary/sample-banner-use.json | 3 +- .../sample-banner-with-mediatype.json | 3 +- .../sample-banner-with-palcementid.json | 3 +- .../algorixtest/exemplary/sample-banner.json | 3 +- .../algorixtest/exemplary/sample-native.json | 3 +- .../algorixtest/exemplary/sample-nobid.json | 3 +- .../exemplary/sample-rewarded-video.json | 3 +- .../algorixtest/exemplary/sample-video.json | 3 +- .../supplemental/bad_response.json | 5 +- .../multiformat-no-mediatype-response.json | 3 +- .../sample-banner-with-other-mediatype.json | 3 +- .../algorixtest/supplemental/status_400.json | 3 +- .../algorixtest/supplemental/status_500.json | 3 +- adapters/algorix/params_test.go | 3 +- adapters/alkimi/alkimi.go | 195 ++ adapters/alkimi/alkimi_test.go | 57 + .../alkimitest/exemplary/simple-audio.json | 144 + .../alkimitest/exemplary/simple-banner.json | 151 + .../alkimitest/exemplary/simple-video.json | 148 + .../supplemental/bad_media_type.json | 109 + .../alkimitest/supplemental/bad_response.json | 102 + .../alkimitest/supplemental/status-204.json | 97 + .../supplemental/status-not-200.json | 102 + adapters/alkimi/params_test.go | 48 + adapters/amx/amx.go | 39 +- adapters/amx/amx_test.go | 39 +- .../amx/amxtest/exemplary/app-simple.json | 3 +- .../amxtest/exemplary/display-multiple.json | 9 +- .../amx/amxtest/exemplary/simple-native.json | 3 +- .../amx/amxtest/exemplary/video-simple.json | 3 +- .../amx/amxtest/exemplary/web-simple.json | 3 +- .../amxtest/supplemental/204-response.json | 3 +- .../amxtest/supplemental/400-response.json | 3 +- .../amxtest/supplemental/500-response.json | 3 +- adapters/amx/params_test.go | 2 +- adapters/apacdex/apacdex.go | 20 +- adapters/apacdex/apacdex_test.go | 6 +- .../exemplary/banner-and-video.json | 3 +- .../apacdex/apacdextest/exemplary/banner.json | 3 +- .../apacdex/apacdextest/exemplary/video.json | 3 +- .../supplemental/explicit-dimensions.json | 3 +- .../invalid-response-no-bids.json | 3 +- .../invalid-response-unmarshall-error.json | 7 +- .../supplemental/server-error-code.json | 3 +- .../supplemental/server-no-content.json | 3 +- .../supplemental/wrong-bid-ext.json | 3 +- .../supplemental/wrong-impression-ext.json | 2 +- adapters/apacdex/params_test.go | 2 +- adapters/appnexus/appnexus.go | 127 +- adapters/appnexus/appnexus_test.go | 6 +- .../appnexustest/amp/simple-banner.json | 3 +- .../appnexustest/amp/simple-video.json | 3 +- .../exemplary/dynamic-keywords-params.json | 3 +- .../appnexustest/exemplary/native-1.1.json | 3 +- .../exemplary/optional-params.json | 3 +- .../exemplary/schain-24-other-ext.json | 3 +- .../appnexustest/exemplary/schain-24.json | 3 +- .../exemplary/schain-25-other-ext.json | 3 +- .../appnexustest/exemplary/schain-25.json | 3 +- .../simple-banner-foreign-currency.json | 3 +- .../appnexustest/exemplary/simple-banner.json | 3 +- .../appnexustest/exemplary/simple-video.json | 3 +- .../exemplary/string-keywords-params.json | 3 +- .../exemplary/video-invalid-category.json | 3 +- .../supplemental/displaymanager-test.json | 3 +- .../supplemental/explicit-dimensions.json | 3 +- .../appnexustest/supplemental/gpid.json | 154 + .../supplemental/invalid-bid-type.json | 3 +- .../supplemental/legacy-params.json | 3 +- .../appnexustest/supplemental/multi-bid.json | 3 +- .../placement-id-as-string-test.json | 3 +- .../request-ext-appnexus-existing.json | 3 +- .../supplemental/reserve-ignored.json | 3 +- .../supplemental/reserve-test.json | 3 +- .../appnexustest/supplemental/status-400.json | 3 +- .../supplemental/usePmtRule-test.json | 3 +- .../video/video-brand-category.json | 3 +- ...deo-no-adpodid-two-imps-different-pod.json | 3 +- .../video-same-adpodid-two-imps-same-pod.json | 3 +- adapters/appnexus/models.go | 10 +- adapters/appnexus/params_test.go | 2 +- adapters/appush/appush.go | 18 +- adapters/appush/appush_test.go | 6 +- .../appushtest/exemplary/endpointId.json | 3 +- .../appushtest/exemplary/simple-banner.json | 3 +- .../appushtest/exemplary/simple-native.json | 3 +- .../appushtest/exemplary/simple-video.json | 3 +- .../exemplary/simple-web-banner.json | 3 +- .../supplemental/bad_media_type.json | 3 +- .../appushtest/supplemental/bad_response.json | 5 +- .../appushtest/supplemental/status-204.json | 3 +- .../supplemental/status-not-200.json | 3 +- adapters/appush/params_test.go | 2 +- adapters/aso/aso.go | 154 + adapters/aso/aso_test.go | 29 + .../aso/asotest/exemplary/app-banner.json | 126 + .../exemplary/app-multi-impressions.json | 249 ++ .../aso/asotest/exemplary/app-native.json | 112 + adapters/aso/asotest/exemplary/app-video.json | 136 + .../aso/asotest/exemplary/site-banner.json | 130 + .../aso/asotest/exemplary/site-native.json | 113 + .../aso/asotest/exemplary/site-video.json | 136 + .../supplemental/bad-request-no-bidder.json | 25 + .../supplemental/bad-request-no-ext.json | 22 + .../aso/asotest/supplemental/bad-request.json | 52 + .../asotest/supplemental/empty-response.json | 42 + .../supplemental/media-type-absent.json} | 51 +- .../supplemental/media-type-mapping.json | 101 + .../asotest/supplemental/server-error.json | 49 + .../supplemental/unparsable-response.json | 49 + adapters/aso/params_test.go | 47 + .../exemplary/banner-app.json | 3 +- .../exemplary/interstitial.json | 3 +- .../exemplary/native-1.1.json | 3 +- .../audienceNetworktest/exemplary/video.json | 3 +- .../exemplary/video_consented_providers.json | 3 +- .../supplemental/banner-format-only.json | 3 +- .../supplemental/invalid-adm.json | 5 +- .../supplemental/missing-adm-bidid.json | 3 +- .../supplemental/missing-adm.json | 3 +- .../supplemental/multi-imp.json | 6 +- .../supplemental/no-bid-204.json | 3 +- .../supplemental/server-error-500.json | 3 +- .../supplemental/split-placementId.json | 3 +- adapters/audienceNetwork/facebook.go | 76 +- adapters/audienceNetwork/facebook_test.go | 8 +- adapters/automatad/automatad.go | 14 +- adapters/automatad/automatad_test.go | 6 +- .../exemplary/simple-banner.json | 3 +- .../supplemental/bad-request.json | 3 +- .../supplemental/error-500-request.json | 3 +- .../supplemental/no-content.json | 3 +- adapters/automatad/params_test.go | 2 +- adapters/avocet/avocet.go | 21 +- adapters/avocet/avocet_test.go | 15 +- .../avocet/avocettest/exemplary/banner.json | 3 +- .../avocet/avocettest/exemplary/video.json | 3 +- adapters/axis/axis.go | 16 +- adapters/axis/axis_test.go | 6 +- .../axistest/exemplary/simple-banner.json | 3 +- .../axistest/exemplary/simple-native.json | 3 +- .../axis/axistest/exemplary/simple-video.json | 3 +- .../axistest/exemplary/simple-web-banner.json | 3 +- .../axistest/supplemental/bad_media_type.json | 3 +- .../axistest/supplemental/bad_response.json | 5 +- .../axistest/supplemental/status-204.json | 3 +- .../axistest/supplemental/status-not-200.json | 3 +- adapters/axis/params_test.go | 2 +- adapters/axonix/axonix.go | 20 +- adapters/axonix/axonix_test.go | 6 +- .../exemplary/banner-and-video.json | 3 +- .../exemplary/banner-video-native.json | 3 +- .../axonixtest/exemplary/simple-banner.json | 3 +- .../axonixtest/exemplary/simple-video.json | 3 +- .../supplemental/bad-response-no-body.json | 7 +- .../supplemental/status-bad-request.json | 3 +- .../supplemental/status-no-content.json | 3 +- .../supplemental/unexpected-status-code.json | 3 +- .../supplemental/valid-extension.json | 3 +- .../supplemental/valid-with-device.json | 3 +- adapters/axonix/params_test.go | 2 +- adapters/beachfront/beachfront.go | 62 +- adapters/beachfront/beachfront_test.go | 6 +- .../exemplary/adm-video-app.json | 3 +- .../beachfronttest/exemplary/adm-video.json | 3 +- .../beachfronttest/exemplary/banner.json | 6 +- .../beachfronttest/exemplary/nurl-video.json | 3 +- .../adm-video-app-alphanum-bundle.json | 3 +- .../adm-video-app-malformed-bundle.json | 3 +- .../supplemental/adm-video-by-explicit.json | 3 +- .../supplemental/adm-video-no-cat.json | 3 +- .../supplemental/adm-video-no-ip.json | 3 +- .../supplemental/adm-video-no-size.json | 125 + .../supplemental/adm-video-partial-size.json | 126 + .../supplemental/adm-video-schain.json | 3 +- .../adm-video-zero-size-partial.json | 127 + .../supplemental/adm-video-zero-size.json | 127 + .../supplemental/banner-204-with-body.json | 3 +- .../supplemental/banner-204.json | 3 +- .../banner-and-adm-video-by-explicit.json | 6 +- ...video-expected-204-response-on-banner.json | 6 +- .../supplemental/banner-and-adm-video.json | 6 +- .../supplemental/banner-and-nurl-video.json | 6 +- .../supplemental/banner-bad-request-400.json | 6 +- .../supplemental/banner-schain.json | 6 +- ...er_response_unmarshal_error_adm_video.json | 3 +- ...idder_response_unmarshal_error_banner.json | 6 +- ...r_response_unmarshal_error_nurl_video.json | 3 +- .../currency-adm-video-converted.json | 3 +- .../currency-adm-video-ext-wins.json | 3 +- .../currency-adm-video-imp-wins.json | 3 +- ...rrency-adm-video-warning-and-fallback.json | 3 +- .../currency-adm-video-zero-by-min-both.json | 3 +- .../currency-adm-video-zero-by-min-ext.json | 3 +- .../currency-adm-video-zero-by-min-imp.json | 3 +- .../internal-server-error-500.json | 3 +- .../supplemental/six-nine-combo.json | 15 +- .../supplemental/two-four-combo.json | 9 +- .../supplemental/unmarshal-error-banner.json | 2 +- .../supplemental/unmarshal-error-video.json | 2 +- adapters/beachfront/params_test.go | 2 +- adapters/beintoo/beintoo.go | 30 +- adapters/beintoo/beintoo_test.go | 6 +- .../beintootest/exemplary/minimal-banner.json | 3 +- .../supplemental/add-bidfloor.json | 3 +- .../supplemental/build-banner-object.json | 3 +- .../invalid-response-no-bids.json | 3 +- .../invalid-response-unmarshall-error.json | 7 +- .../supplemental/server-error-code.json | 3 +- .../supplemental/server-no-content.json | 3 +- .../site-domain-and-url-correctly-parsed.json | 3 +- adapters/beintoo/params_test.go | 2 +- adapters/bematterfull/bematterfull.go | 22 +- .../bematterfulltest/exemplary/banner.json | 6 +- .../bematterfulltest/exemplary/native.json | 3 +- .../bematterfulltest/exemplary/video.json | 3 +- .../supplemental/bad-response.json | 5 +- .../supplemental/empty-mediatype.json | 3 +- .../supplemental/empty-seatbid-0-bid.json | 3 +- .../supplemental/empty-seatbid.json | 3 +- .../invalid-ext-bidder-object.json | 2 +- .../supplemental/invalid-ext-object.json | 2 +- .../supplemental/invalid-mediatype.json | 3 +- .../supplemental/status-204.json | 3 +- .../supplemental/status-400.json | 3 +- .../supplemental/status-503.json | 3 +- .../supplemental/unexpected-status.json | 3 +- .../bematterfull/bematterfulltest_test.go | 6 +- adapters/bematterfull/params_test.go | 2 +- adapters/between/between.go | 30 +- adapters/between/between_test.go | 6 +- .../betweentest/exemplary/multi-request.json | 3 +- .../betweentest/exemplary/secure-detect.json | 3 +- .../exemplary/simple-site-banner.json | 3 +- .../supplemental/bad-dsp-request-example.json | 3 +- .../supplemental/bad-response-body.json | 5 +- .../dsp-server-internal-error-example.json | 3 +- .../betweentest/supplemental/no-bids.json | 3 +- .../unknown-status-code-example.json | 3 +- adapters/between/params_test.go | 2 +- adapters/beyondmedia/beyondmedia.go | 18 +- adapters/beyondmedia/beyondmedia_test.go | 6 +- .../exemplary/simple-banner.json | 3 +- .../exemplary/simple-native.json | 3 +- .../exemplary/simple-video.json | 3 +- .../exemplary/simple-web-banner.json | 3 +- .../supplemental/bad_media_type.json | 3 +- .../supplemental/bad_response.json | 5 +- .../supplemental/status-204.json | 3 +- .../supplemental/status-not-200.json | 3 +- adapters/beyondmedia/params_test.go | 2 +- adapters/bidder.go | 13 +- adapters/bidmachine/bidmachine.go | 24 +- adapters/bidmachine/bidmachine_test.go | 6 +- .../rewarded_interstitial_no_battr.json | 3 +- .../rewarded_interstitial_w_battr.json | 3 +- .../exemplary/rewarded_video_no_battr.json | 3 +- .../exemplary/rewarded_video_w_battr.json | 3 +- .../exemplary/simple_banner.json | 3 +- .../exemplary/simple_interstitial.json | 3 +- .../exemplary/simple_video.json | 3 +- .../supplemental/status-204.json | 3 +- .../supplemental/status-400.json | 3 +- .../supplemental/status-403.json | 3 +- .../supplemental/status-408.json | 3 +- .../supplemental/status-500.json | 3 +- .../supplemental/status-502.json | 3 +- .../supplemental/status-503.json | 3 +- .../supplemental/status-504.json | 3 +- adapters/bidmachine/params_test.go | 2 +- adapters/bidmatic/bidmatic.go | 207 ++ adapters/bidmatic/bidmatic_test.go | 23 + .../exemplary/media-type-mapping.json | 91 + .../bidmatictest/exemplary/simple-banner.json | 98 + .../bidmatictest/exemplary/simple-video.json} | 16 +- .../supplemental/explicit-dimensions.json | 60 + .../supplemental/imp-ext-empty.json | 21 + .../supplemental/wrong-impression-ext.json | 26 + .../wrong-impression-mapping.json | 11 +- .../supplemental/wrong-response.json | 65 + adapters/bidmatic/params_test.go | 64 + adapters/bidmyadz/bidmyadz.go | 16 +- adapters/bidmyadz/bidmyadz_test.go | 6 +- .../bidmyadztest/exemplary/banner.json | 3 +- .../bidmyadztest/exemplary/native.json | 3 +- .../bidmyadztest/exemplary/video.json | 3 +- .../supplemental/missing-mediatype.json | 7 +- .../supplemental/response-without-bids.json | 6 +- .../response-without-seatbid.json | 3 +- .../bidmyadztest/supplemental/status-204.json | 3 +- .../bidmyadztest/supplemental/status-400.json | 3 +- .../status-service-unavailable.json | 3 +- .../supplemental/status-unknown.json | 3 +- adapters/bidmyadz/params_test.go | 2 +- adapters/bidscube/bidscube.go | 16 +- adapters/bidscube/bidscube_test.go | 6 +- .../bidscubetest/exemplary/simple-banner.json | 3 +- .../bidscubetest/exemplary/simple-native.json | 3 +- .../bidscubetest/exemplary/simple-video.json | 3 +- .../exemplary/simple-web-banner.json | 3 +- .../supplemental/bad_bidtype_response.json | 3 +- .../supplemental/bad_response.json | 5 +- .../supplemental/bad_status_code.json | 3 +- .../supplemental/imp_ext_string.json | 2 +- .../bidscubetest/supplemental/status-204.json | 3 +- .../bidscubetest/supplemental/status-404.json | 3 +- adapters/bidscube/params_test.go | 2 +- adapters/bidstack/bidstack.go | 16 +- adapters/bidstack/bidstack_test.go | 8 +- .../exemplary/simple-app-video.json | 3 +- .../bidstacktest/supplemental/status-204.json | 3 +- .../bidstacktest/supplemental/status-400.json | 3 +- .../bidstacktest/supplemental/status-404.json | 3 +- adapters/bidstack/params_test.go | 2 +- adapters/bigoad/bigoad.go | 157 + adapters/bigoad/bigoad_test.go | 29 + .../bigoadtest/exemplary/banner_app.json | 186 ++ .../bigoadtest/exemplary/banner_web.json | 135 + .../bigoadtest/exemplary/native_app.json | 147 + .../bigoadtest/exemplary/native_web.json | 134 + .../bigoadtest/exemplary/video_app.json | 160 + .../bigoadtest/exemplary/video_web.json | 158 + .../supplemental/empty_seatbid.json | 125 + .../supplemental/invalid_mtype.json | 164 + .../supplemental/invalid_request.json | 31 + .../invalid_request_bigo_ext.json | 37 + .../supplemental/invalid_response.json | 111 + .../params_test.go => bigoad/param_test.go} | 17 +- .../bizzclick.go => blasto/blasto.go} | 37 +- .../blasto_test.go} | 16 +- .../blastotest}/exemplary/banner-app.json | 11 +- .../blastotest}/exemplary/banner-web.json | 42 +- .../exemplary/default-host-param.json | 153 + .../blastotest/exemplary/native-app.json | 149 + .../blastotest/exemplary/native-web.json | 136 + .../blastotest}/exemplary/video-app.json | 17 +- .../blastotest}/exemplary/video-web.json | 13 +- .../supplemental/empty-seatbid-array.json | 22 +- .../invalid-blasto-ext-object.json | 29 + .../supplemental/invalid-response.json | 115 + .../supplemental/status-code-bad-request.json | 96 + .../supplemental/status-code-no-content.json | 79 + .../supplemental/status-code-other-error.json | 84 + .../status-code-service-unavailable.json | 84 + adapters/{bizzclick => blasto}/params_test.go | 12 +- adapters/bliink/bliink.go | 14 +- adapters/bliink/bliink_test.go | 6 +- .../bliink/bliinktest/exemplary/banner.json | 3 +- .../exemplary/banner_native_video.json | 3 +- .../bliink/bliinktest/exemplary/native.json | 3 +- .../bliink/bliinktest/exemplary/video.json | 3 +- .../supplemental/multiple_format_request.json | 3 +- .../supplemental/no_seat_bid_in_response.json | 3 +- .../supplemental/status_bad_request.json | 3 +- .../supplemental/status_no_content.json | 3 +- .../supplemental/status_not_ok.json | 3 +- adapters/bliink/params_test.go | 3 +- adapters/blue/blue.go | 14 +- adapters/blue/blue_test.go | 6 +- .../bluetest/exemplary/simple-web-banner.json | 3 +- .../204-response-from-target.json | 3 +- .../400-response-from-target.json | 3 +- .../500-response-from-target.json | 3 +- .../bluetest/supplemental/bad_response.json | 5 +- adapters/bluesea/bluesea.go | 22 +- adapters/bluesea/bluesea_test.go | 6 +- .../bluesea/blueseatest/exemplary/banner.json | 3 +- .../bluesea/blueseatest/exemplary/native.json | 3 +- .../bluesea/blueseatest/exemplary/nobid.json | 3 +- .../blueseatest/exemplary/site-banner.json | 133 + .../blueseatest/exemplary/site-native.json | 133 + .../blueseatest/exemplary/site-video.json | 163 + .../bluesea/blueseatest/exemplary/video.json | 3 +- .../supplemental/invalid-media-type.json | 3 +- .../supplemental/malformed-body-response.json | 3 +- .../supplemental/status-400-response.json | 3 +- adapters/bluesea/params_test.go | 2 +- adapters/bmtm/brightmountainmedia.go | 18 +- adapters/bmtm/brightmountainmedia_test.go | 6 +- .../exemplary/app-banner.json | 106 + .../exemplary/app-video.json | 116 + .../exemplary/banner.json | 3 +- .../exemplary/multi-imp.json | 6 +- .../exemplary/video.json | 3 +- .../multi-imp-mixed-validation.json | 3 +- .../supplemental/status-not-ok.json | 3 +- adapters/bmtm/params_test.go | 2 +- adapters/boldwin/boldwin.go | 47 +- adapters/boldwin/boldwin_test.go | 6 +- .../boldwintest/exemplary/endpointId.json | 5 +- .../boldwintest/exemplary/simple-banner.json | 5 +- .../boldwintest/exemplary/simple-native.json | 5 +- .../boldwintest/exemplary/simple-video.json | 5 +- .../exemplary/simple-web-banner.json | 5 +- .../supplemental/bad_media_type.json | 5 +- .../supplemental/bad_response.json | 5 +- .../boldwintest/supplemental/status-204.json | 3 +- .../supplemental/status-not-200.json | 3 +- adapters/boldwin/params_test.go | 2 +- adapters/brave/brave.go | 20 +- adapters/brave/brave_test.go | 6 +- .../brave/bravetest/exemplary/banner-app.json | 3 +- .../brave/bravetest/exemplary/banner-web.json | 3 +- .../brave/bravetest/exemplary/native-app.json | 3 +- .../brave/bravetest/exemplary/native-web.json | 3 +- .../brave/bravetest/exemplary/video-app.json | 3 +- .../brave/bravetest/exemplary/video-web.json | 3 +- .../supplemental/empty-seatbid-array.json | 3 +- .../supplemental/invalid-response.json | 3 +- .../supplemental/status-code-bad-request.json | 3 +- .../supplemental/status-code-no-content.json | 3 +- .../supplemental/status-code-other-error.json | 3 +- .../status-code-service-unavailable.json | 3 +- adapters/brave/params_test.go | 2 +- adapters/bwx/bwx.go | 160 + adapters/bwx/bwx_test.go | 34 + adapters/bwx/bwxtest/exemplary/banner.json | 283 ++ adapters/bwx/bwxtest/exemplary/native.json | 165 + adapters/bwx/bwxtest/exemplary/video.json | 205 ++ .../bwxtest/supplemental/bad-response.json | 107 + .../bwxtest/supplemental/empty-mediatype.json | 191 ++ .../supplemental/empty-seatbid-0-bid.json | 112 + .../bwxtest/supplemental/empty-seatbid.json | 112 + .../invalid-ext-bidder-object.json | 49 + .../supplemental/invalid-ext-object.json | 47 + .../supplemental/invalid-mediatype.json | 188 ++ .../bwx/bwxtest/supplemental/status-204.json | 101 + .../bwx/bwxtest/supplemental/status-400.json | 107 + .../bwx/bwxtest/supplemental/status-503.json | 106 + .../supplemental/unexpected-status.json | 107 + adapters/bwx/params_test.go | 54 + .../exemplary/banner-and-video-app.json | 3 +- .../exemplary/banner-and-video-site.json | 3 +- .../exemplary/banner-app.json | 3 +- .../exemplary/minimal-banner.json | 3 +- .../exemplary/video-app.json | 3 +- .../exemplary/video-ctv.json | 3 +- .../exemplary/video-site.json | 3 +- .../supplemental/add-bidfloor.json | 3 +- .../app-domain-and-url-correctly-parsed.json | 3 +- .../app-storeUrl-correctly-parsed.json | 3 +- .../bad-imp-video-missing-partial-sizes.json | 146 + .../bad-imp-video-zero-sizes.json | 38 + .../supplemental/build-banner-object.json | 3 +- .../supplemental/build-video-object.json | 3 +- .../invalid-response-no-bids.json | 3 +- .../invalid-response-unmarshall-error.json | 7 +- .../supplemental/server-error-code.json | 3 +- .../supplemental/server-no-content.json | 3 +- .../site-domain-and-url-correctly-parsed.json | 3 +- .../cadent_aperture_mx/cadentaperturemx.go | 38 +- .../cadentaperturemx_test.go | 8 +- adapters/cadent_aperture_mx/params_test.go | 2 +- adapters/ccx/ccx.go | 14 +- adapters/ccx/ccx_test.go | 6 +- .../ccx/ccxtest/exemplary/multi-banner.json | 3 +- .../ccx/ccxtest/exemplary/simple-banner.json | 3 +- .../204-response-from-target.json | 3 +- .../400-response-from-target.json | 3 +- .../500-response-from-target.json | 3 +- adapters/ccx/params_test.go | 2 +- adapters/cointraffic/cointraffic.go | 79 + adapters/cointraffic/cointraffic_test.go | 51 + .../exemplary/simple-banner.json | 158 + .../supplemental/bad-request.json | 70 + .../cointraffictest/supplemental/no-bid.json | 65 + .../supplemental/server-error.json | 70 + adapters/cointraffic/params_test.go | 51 + adapters/coinzilla/coinzilla.go | 14 +- adapters/coinzilla/coinzilla_test.go | 6 +- .../exemplary/multi-banners.json | 3 +- .../coinzillatest/exemplary/multi-imp.json | 3 +- .../exemplary/single-banner.json | 3 +- .../supplemental/bad-request.json | 3 +- .../coinzillatest/supplemental/no-bid.json | 3 +- adapters/coinzilla/params_test.go | 2 +- adapters/colossus/colossus.go | 16 +- adapters/colossus/colossus_test.go | 6 +- .../exemplary/simple-banner-groupId.json | 3 +- .../exemplary/simple-banner-without-ext.json | 3 +- .../colossustest/exemplary/simple-banner.json | 3 +- .../exemplary/simple-native-without-ext.json | 3 +- .../colossustest/exemplary/simple-native.json | 3 +- .../exemplary/simple-video-without-ext.json | 3 +- .../colossustest/exemplary/simple-video.json | 3 +- .../exemplary/simple-web-banner.json | 3 +- .../supplemental/bad_media_type.json | 3 +- .../supplemental/bad_response.json | 5 +- .../supplemental/bad_status_code.json | 3 +- .../colossustest/supplemental/status-204.json | 3 +- .../colossustest/supplemental/status-404.json | 3 +- adapters/colossus/params_test.go | 2 +- adapters/compass/compass.go | 18 +- adapters/compass/compass_test.go | 6 +- .../compasstest/exemplary/endpointId.json | 3 +- .../compasstest/exemplary/simple-banner.json | 3 +- .../compasstest/exemplary/simple-native.json | 3 +- .../compasstest/exemplary/simple-video.json | 3 +- .../exemplary/simple-web-banner.json | 3 +- .../supplemental/bad_media_type.json | 3 +- .../supplemental/bad_response.json | 5 +- .../compasstest/supplemental/status-204.json | 3 +- .../supplemental/status-not-200.json | 3 +- adapters/compass/params_test.go | 2 +- adapters/concert/concert.go | 140 + adapters/concert/concert_test.go | 20 + .../concert/concerttest/exemplary/audio.json | 119 + .../concert/concerttest/exemplary/banner.json | 139 + .../concert/concerttest/exemplary/video.json | 129 + adapters/concert/params_test.go | 51 + adapters/connectad/connectad.go | 30 +- adapters/connectad/connectad_test.go | 6 +- .../exemplary/optional-params.json | 3 +- .../exemplary/simple-banner.json | 3 +- .../connectadtest/supplemental/204.json | 3 +- .../supplemental/badresponse.json | 5 +- .../supplemental/banner-multi.json | 3 +- .../connectadtest/supplemental/err500.json | 3 +- .../connectadtest/supplemental/ipv6.json | 3 +- .../connectadtest/supplemental/no_device.json | 3 +- .../connectadtest/supplemental/no_dnt.json | 3 +- .../connectadtest/supplemental/no_ext.json | 4 +- adapters/connectad/params_test.go | 6 +- adapters/consumable/adtypes.go | 64 - adapters/consumable/consumable.go | 393 +-- .../consumable/exemplary/app-audio.json | 114 + .../consumable/exemplary/app-banner.json | 112 + .../consumable/exemplary/app-video.json | 127 + .../consumable/exemplary/simple-banner.json | 136 +- .../supplemental/app-audio-bad-params.json | 37 + .../supplemental/app-banner-no-ad.json | 76 + .../supplemental/app-video-no-media-type.json | 111 + .../supplemental/bad-dsp-request-example.json | 79 + .../dsp-server-internal-error-example.json | 79 + .../simple-banner-content-meta.json | 120 +- .../supplemental/simple-banner-coppa.json | 113 +- .../supplemental/simple-banner-eids.json | 166 +- .../supplemental/simple-banner-gdpr-2.json | 115 +- .../supplemental/simple-banner-gdpr-3.json | 122 +- .../supplemental/simple-banner-gdpr.json | 129 +- .../supplemental/simple-banner-gpp.json | 128 +- .../supplemental/simple-banner-meta.json | 117 +- .../simple-banner-no-impressionUrl.json | 133 +- .../supplemental/simple-banner-no-params.json | 43 + .../supplemental/simple-banner-schain.json | 135 +- .../simple-banner-us-privacy.json | 122 +- .../unknown-status-code-example.json | 79 + adapters/consumable/consumable_test.go | 96 +- adapters/consumable/instant.go | 21 - adapters/consumable/params_test.go | 4 +- adapters/consumable/retrieveAd.go | 10 - adapters/conversant/conversant.go | 45 +- adapters/conversant/conversant_test.go | 6 +- .../conversanttest/exemplary/banner.json | 3 +- .../conversanttest/exemplary/simple_app.json | 3 +- .../conversanttest/exemplary/video.json | 30 +- .../supplemental/server_badresponse.json | 3 +- .../supplemental/server_nocontent.json | 3 +- .../supplemental/server_unknownstatus.json | 3 +- .../supplemental/test_params.json | 3 +- adapters/copper6ssp/copper6ssp.go | 158 + adapters/copper6ssp/copper6ssp_test.go | 20 + .../copper6ssptest/exemplary/endpointId.json | 136 + .../exemplary/multi-format.json | 105 + .../copper6ssptest/exemplary/multi-imp.json | 253 ++ .../exemplary/simple-banner.json | 136 + .../exemplary/simple-native.json | 120 + .../exemplary/simple-video.json | 131 + .../exemplary/simple-web-banner.json | 136 + .../supplemental/bad_media_type.json | 83 + .../supplemental/bad_response.json | 85 + .../supplemental/no-valid-bidder-param.json | 42 + .../supplemental/no-valid-imp-ext.json | 38 + .../supplemental/status-204.json | 80 + .../supplemental/status-not-200.json | 85 + adapters/copper6ssp/params_test.go | 47 + adapters/cpmstar/cpmstar.go | 26 +- adapters/cpmstar/cpmstar_test.go | 6 +- .../exemplary/banner-and-video.json | 3 +- .../cpmstar/cpmstartest/exemplary/banner.json | 3 +- .../exemplary/multiple-banners.json | 154 + .../cpmstar/cpmstartest/exemplary/video.json | 3 +- .../supplemental/explicit-dimensions.json | 3 +- .../invalid-response-no-bids.json | 3 +- .../invalid-response-unmarshall-error.json | 7 +- .../supplemental/server-error-code.json | 3 +- .../supplemental/server-no-content.json | 3 +- .../supplemental/wrong-impression-ext.json | 4 +- .../wrong-impression-mapping.json | 3 +- adapters/cpmstar/params_test.go | 2 +- adapters/criteo/criteo.go | 62 +- adapters/criteo/criteo_test.go | 42 +- .../exemplary/simple-banner-cookie-uid.json | 3 +- .../exemplary/simple-banner-inapp.json | 3 +- .../simple-banner-multiple-bids.json | 3 +- .../exemplary/simple-banner-paapi.json | 176 + .../exemplary/simple-banner-uid.json | 3 +- .../exemplary/simple-multi-type-banner.json | 3 +- .../exemplary/simple-multi-type-video.json | 3 +- .../criteotest/exemplary/simple-native.json | 94 + .../criteotest/exemplary/simple-video.json | 3 +- .../204-response-from-target.json | 3 +- .../400-response-from-target.json | 3 +- .../500-response-from-target.json | 3 +- .../supplemental/simple-banner-with-ipv6.json | 3 +- adapters/criteo/params_test.go | 2 +- adapters/cwire/cwire.go | 14 +- adapters/cwire/cwire_test.go | 6 +- .../cwiretest/exemplary/banner-imp-ext.json | 3 +- .../cwire/cwiretest/exemplary/banner.json | 3 +- .../cwire/cwiretest/supplemental/204.json | 3 +- .../cwire/cwiretest/supplemental/500.json | 3 +- .../cwiretest/supplemental/badresponse.json | 3 +- adapters/cwire/params_test.go | 2 +- adapters/datablocks/datablocks.go | 22 +- adapters/datablocks/datablocks_test.go | 6 +- .../exemplary/multi-request.json | 3 +- .../datablockstest/exemplary/native.json | 3 +- .../exemplary/simple-banner.json | 3 +- .../exemplary/simple-video.json | 3 +- .../supplemental/bad-response-body.json | 5 +- .../supplemental/bad-server-response.json | 3 +- .../supplemental/missing-ext.json | 4 +- .../supplemental/no-content-response.json | 3 +- adapters/decenterads/decenterads.go | 16 +- adapters/decenterads/decenterads_test.go | 6 +- .../exemplary/simple-banner.json | 3 +- .../exemplary/simple-video.json | 3 +- .../exemplary/simple-web-banner.json | 3 +- .../supplemental/bad_response.json | 5 +- .../supplemental/bad_status_code.json | 3 +- .../supplemental/imp_ext_string.json | 2 +- .../supplemental/status-204.json | 3 +- .../supplemental/status-404.json | 3 +- adapters/decenterads/params_test.go | 2 +- adapters/deepintent/deepintent.go | 22 +- adapters/deepintent/deepintent_test.go | 6 +- .../exemplary/simple-banner.json | 3 +- .../exemplary/simple-web-banner.json | 3 +- .../supplemental/bad_response.json | 5 +- .../deepintenttest/supplemental/no_ext-1.json | 2 +- .../deepintenttest/supplemental/no_ext.json | 4 +- .../supplemental/status-204.json | 3 +- .../supplemental/status-404.json | 3 +- .../deepintenttest/supplemental/wrongimp.json | 3 +- adapters/deepintent/params_test.go | 2 +- adapters/definemedia/definemedia.go | 18 +- adapters/definemedia/definemedia_test.go | 6 +- .../exemplary/sample-conative-banner.json | 3 +- .../exemplary/sample-conative-native.json | 3 +- .../supplemental/nobid-response.json | 3 +- .../supplemental/nocontent-response.json | 3 +- .../supplemental/status_400.json | 3 +- .../supplemental/status_418.json | 3 +- .../supplemental/unmarshal-error.json | 5 +- .../supplemental/unsupported-type.json | 3 +- adapters/definemedia/params_test.go | 2 +- adapters/dianomi/dianomi.go | 22 +- adapters/dianomi/dianomi_test.go | 6 +- .../dianomitest/exemplary/multi-format.json | 3 +- .../dianomitest/exemplary/multi-native.json | 3 +- ...gle-banner-pricetype-gross-extend-ext.json | 3 +- .../single-banner-pricetype-gross.json | 3 +- .../single-banner-pricetype-net.json | 3 +- .../dianomitest/exemplary/single-banner.json | 3 +- .../dianomitest/exemplary/single-native.json | 3 +- .../dianomitest/exemplary/single-video.json | 3 +- ...nners-different-pricetypes-extend-ext.json | 3 +- .../two-banners-different-pricetypes.json | 3 +- .../dianomitest/supplemental/bad-request.json | 3 +- .../supplemental/empty-response.json | 3 +- .../supplemental/invalid-imp-mediatype.json | 3 +- .../supplemental/nobid-response.json | 3 +- .../supplemental/server-error.json | 3 +- .../supplemental/unparsable-response.json | 5 +- adapters/dianomi/params_test.go | 2 +- adapters/displayio/displayio.go | 189 ++ adapters/displayio/displayio_test.go | 22 + .../displayiotest/exemplary/multi-format.json | 147 + .../displayiotest/exemplary/multi-imp.json | 211 ++ .../exemplary/simple-banner.json | 117 + .../displayiotest/exemplary/simple-video.json | 124 + .../supplemental/bad-response.json | 77 + .../supplemental/currency-conversion.json | 95 + .../displayiotest/supplemental/ext.json | 30 + .../supplemental/nobid-response.json | 72 + .../supplemental/response-code-invalid.json | 74 + .../supplemental/seatbid-response.json | 77 + .../supplemental/unexpected-media-type.json | 105 + adapters/displayio/params_test.go | 48 + adapters/dmx/dmx.go | 61 +- adapters/dmx/dmx_test.go | 14 +- .../dmx/dmxtest/exemplary/idfa-to-app-id.json | 3 +- .../exemplary/imp-populated-banner.json | 3 +- .../exemplary/missing-width-height.json | 3 +- .../dmx/dmxtest/exemplary/simple-app.json | 3 +- .../dmx/dmxtest/exemplary/simple-banner.json | 3 +- .../dmx/dmxtest/exemplary/simple-video.json | 3 +- adapters/dmx/params_test.go | 3 +- adapters/driftpixel/driftpixel.go | 160 + adapters/driftpixel/driftpixel_test.go | 34 + .../driftpixeltest/exemplary/banner.json | 283 ++ .../exemplary/multi-format.json | 397 +++ .../exemplary/multi-imp-banner.json | 283 ++ .../driftpixeltest/exemplary/native.json | 165 + .../driftpixeltest/exemplary/video.json | 205 ++ .../supplemental/bad-response.json | 107 + .../supplemental/empty-mediatype.json | 191 ++ .../supplemental/empty-seatbid-0-bid.json | 112 + .../supplemental/empty-seatbid.json | 112 + .../invalid-ext-bidder-object.json | 49 + .../supplemental/invalid-ext-object.json | 47 + .../supplemental/invalid-mediatype.json | 188 ++ .../supplemental/status-204.json | 101 + .../supplemental/status-400.json | 107 + .../supplemental/status-503.json | 106 + .../supplemental/unexpected-status.json | 107 + adapters/driftpixel/params_test.go | 54 + adapters/dxkulture/dxkulture.go | 18 +- adapters/dxkulture/dxkulture_test.go | 6 +- .../dxkulturetest/exemplary/banner.json | 5 +- .../exemplary/empty-site-domain-ref.json | 5 +- .../dxkulturetest/exemplary/ipv6.json | 5 +- .../exemplary/video-test-request.json | 5 +- .../dxkulturetest/exemplary/video.json | 5 +- .../supplemental/invalid-imp-ext-bidder.json | 2 +- .../supplemental/invalid-imp-ext.json | 2 +- .../supplemental/invalid-response.json | 5 +- .../dxkulturetest/supplemental/no-mtype.json | 5 +- .../supplemental/status-code-bad-request.json | 5 +- .../supplemental/status-code-no-content.json | 5 +- .../supplemental/status-code-other-error.json | 5 +- adapters/dxkulture/params_test.go | 2 +- adapters/e_volution/evolution.go | 18 +- adapters/e_volution/evolution_test.go | 6 +- .../exemplary/banner-without-mediatype.json | 3 +- .../evolutiontest/exemplary/banner.json | 3 +- .../evolutiontest/exemplary/native.json | 3 +- .../evolutiontest/exemplary/video.json | 3 +- .../supplemental/bad-response.json | 5 +- .../supplemental/empty-seatbid.json | 3 +- .../supplemental/status-204.json | 3 +- .../supplemental/status-400.json | 3 +- .../supplemental/status-503.json | 3 +- .../supplemental/unexpected-status.json | 3 +- adapters/e_volution/params_test.go | 2 +- adapters/edge226/edge226.go | 16 +- adapters/edge226/edge226_test.go | 6 +- .../edge226test/exemplary/endpointId.json | 3 +- .../edge226test/exemplary/simple-banner.json | 3 +- .../edge226test/exemplary/simple-native.json | 3 +- .../edge226test/exemplary/simple-video.json | 3 +- .../exemplary/simple-web-banner.json | 3 +- .../supplemental/bad_media_type.json | 3 +- .../supplemental/bad_response.json | 5 +- .../edge226test/supplemental/status-204.json | 3 +- .../supplemental/status-not-200.json | 3 +- adapters/edge226/params_test.go | 2 +- adapters/emtv/emtv.go | 18 +- adapters/emtv/emtv_test.go | 6 +- .../emtv/emtvtest/exemplary/endpointId.json | 3 +- .../emtvtest/exemplary/simple-banner.json | 3 +- .../emtvtest/exemplary/simple-native.json | 3 +- .../emtv/emtvtest/exemplary/simple-video.json | 3 +- .../emtvtest/exemplary/simple-web-banner.json | 3 +- .../emtvtest/supplemental/bad_media_type.json | 3 +- .../emtvtest/supplemental/bad_response.json | 5 +- .../emtvtest/supplemental/status-204.json | 3 +- .../emtvtest/supplemental/status-not-200.json | 3 +- adapters/emtv/params_test.go | 2 +- adapters/eplanning/eplanning.go | 25 +- adapters/eplanning/eplanning_test.go | 8 +- .../exemplary/simple-banner-2.json | 3 +- .../exemplary/simple-banner.json | 3 +- .../eplanningtest/exemplary/two-banners.json | 3 +- .../app-domain-and-url-correctly-parsed.json | 3 +- .../supplemental/bad-imp-ext.json | 2 +- ...anner-2-sizes-in-priority-list-mobile.json | 3 +- ...nner-3-sizes-in-priority-list-desktop.json | 3 +- ...anner-3-sizes-in-priority-list-mobile.json | 3 +- ...er-3-sizes-inout-priority-list-mobile.json | 3 +- ...nner-4-sizes-in-priority-list-desktop.json | 3 +- .../banner-no-size-sends-1x1.json | 3 +- .../invalid-response-no-bids.json | 3 +- .../invalid-response-unmarshall-error.json | 7 +- .../supplemental/server-bad-request.json | 3 +- .../supplemental/server-error-code.json | 3 +- .../supplemental/server-no-content.json | 3 +- .../site-domain-and-url-correctly-parsed.json | 3 +- .../site-page-and-url-correctly-parsed.json | 3 +- ...eo_outstream-only-send-video-instream.json | 3 +- ...d-1_video_instream-send-only-instream.json | 3 +- ...1_video_outstream-send-only-outstream.json | 3 +- ...am-and-1_outstream-send-only-instream.json | 3 +- .../supplemental/two-imps-video-instream.json | 3 +- .../video-no-placement-send-vctx-2.json | 3 +- .../video-no-size-send-640x480.json | 3 +- .../video-partial-size-send-640x480.json | 72 + .../video-placement-1-send-vctx-1.json | 3 +- .../video-placement-no-1-send-vctx-2.json | 3 +- .../video-zero-size-send-640x480.json | 73 + adapters/epom/epom.go | 14 +- adapters/epom/epom_test.go | 6 +- .../epomtest/exemplary/simple-app-banner.json | 3 +- .../epomtest/exemplary/simple-app-native.json | 3 +- .../epomtest/exemplary/simple-app-video.json | 3 +- .../exemplary/simple-site-banner.json | 3 +- .../exemplary/simple-site-native.json | 3 +- .../epomtest/exemplary/simple-site-video.json | 3 +- .../bad-response-bad-request-error.json | 3 +- .../bad-response-no-bid-obj-error.json | 3 +- .../bad-response-no-seatbid-error.json | 3 +- .../bad-response-server-internal-error.json | 3 +- .../bad-response-unexpected-error.json | 3 +- adapters/escalax/escalax.go | 163 + adapters/escalax/escalax_test.go | 28 + .../escalaxtest/exemplary/banner-app.json | 155 + .../escalaxtest/exemplary/banner-web.json | 203 ++ .../escalaxtest/exemplary/native-app.json | 151 + .../escalaxtest/exemplary/native-web.json | 138 + .../escalaxtest/exemplary/video-app.json | 164 + .../escalaxtest/exemplary/video-web.json | 162 + .../supplemental/bad_media_type.json | 139 + .../supplemental/empty-seatbid-array.json | 133 + .../invalid-bidder-ext-object.json | 33 + .../supplemental/invalid-ext-object.json | 31 + .../supplemental/invalid-response.json | 115 + .../supplemental/status-code-bad-request.json | 96 + .../supplemental/status-code-other-error.json | 84 + adapters/escalax/params_test.go | 52 + adapters/flipp/flipp.go | 77 +- adapters/flipp/flipp_params.go | 2 +- adapters/flipp/flipp_test.go | 66 +- .../exemplary/simple-banner-dtx.json | 3 +- ...ple-banner-native-param-transmit-eids.json | 182 + ...simple-banner-native-param-user-coppa.json | 178 + .../simple-banner-native-param-user-gdpr.json | 178 + .../simple-banner-native-param-user.json | 8 +- .../exemplary/simple-banner-native.json | 3 +- .../flipptest/supplemental/bad-response.json | 5 +- .../supplemental/status-not-200.json | 3 +- adapters/flipp/params_test.go | 2 +- adapters/freewheelssp/freewheelssp.go | 35 +- adapters/freewheelssp/freewheelssp_test.go | 7 +- .../freewheelssptest/exemplary/multi-imp.json | 33 +- .../exemplary/single-imp.json | 3 +- .../exemplary/string-single-imp.json | 3 +- .../supplemental/204-bid-response.json | 3 +- .../supplemental/503-bid-response.json | 3 +- adapters/frvradn/frvradn.go | 20 +- adapters/frvradn/frvradn_test.go | 9 +- .../frvradn/frvradntest/exemplary/banner.json | 3 +- .../frvradn/frvradntest/exemplary/native.json | 3 +- .../frvradn/frvradntest/exemplary/video.json | 3 +- .../frvradn/frvradntest/supplemental/204.json | 3 +- .../frvradn/frvradntest/supplemental/400.json | 3 +- .../frvradn/frvradntest/supplemental/503.json | 3 +- .../supplemental/currency_converter.json | 3 +- .../supplemental/empty_object_response.json | 3 +- .../supplemental/empty_reponse.json | 3 +- .../supplemental/missing_bid_ext.json | 3 +- .../supplemental/unknown_imp_media_type.json | 3 +- adapters/frvradn/params_test.go | 2 +- adapters/gamma/gamma.go | 29 +- adapters/gamma/gamma_test.go | 6 +- .../exemplary/banner-and-video-and-audio.json | 6 +- .../gammatest/exemplary/simple-banner.json | 3 +- .../gammatest/exemplary/valid-extension.json | 3 +- .../exemplary/valid-full-params.json | 3 +- .../gammatest/supplemental/bad-request.json | 3 +- .../supplemental/bad-response-no-body.json | 7 +- .../gammatest/supplemental/ignore-imp.json | 6 +- .../gammatest/supplemental/missing-adm.json | 3 +- .../supplemental/nobid-signaling.json | 3 +- .../supplemental/status-forbidden.json | 3 +- .../supplemental/status-no-content.json | 3 +- adapters/gamma/params_test.go | 2 +- adapters/gamoshi/gamoshi.go | 20 +- adapters/gamoshi/gamoshi_test.go | 6 +- .../exemplary/banner-and-audio.json | 3 +- .../exemplary/banner-and-video.json | 3 +- .../exemplary/banner-native-audio.json | 3 +- .../exemplary/banner-video-native.json | 3 +- .../gamoshitest/exemplary/simple-banner.json | 3 +- .../gamoshitest/exemplary/simple-video.json | 3 +- .../exemplary/valid-extension.json | 3 +- .../exemplary/valid-with-device.json | 3 +- .../exemplary/video-and-audio.json | 3 +- .../supplemental/bad-response-no-body.json | 7 +- .../supplemental/status-bad-request.json | 3 +- .../supplemental/status-no-content.json | 3 +- .../supplemental/unexpected-status-code.json | 3 +- adapters/gamoshi/params_test.go | 2 +- adapters/globalsun/globalsun.go | 14 +- adapters/globalsun/globalsun_test.go | 6 +- .../exemplary/simple-banner.json | 3 +- .../exemplary/simple-native.json | 3 +- .../globalsuntest/exemplary/simple-video.json | 3 +- .../exemplary/simple-web-banner.json | 3 +- .../supplemental/bad_media_type.json | 3 +- .../supplemental/bad_response.json | 5 +- .../supplemental/status-204.json | 3 +- .../supplemental/status-not-200.json | 3 +- adapters/globalsun/params_test.go | 2 +- adapters/gothamads/gothamads.go | 20 +- adapters/gothamads/gothamads_test.go | 6 +- .../gothamadstest/exemplary/banner-app.json | 3 +- .../gothamadstest/exemplary/banner-web.json | 3 +- .../gothamadstest/exemplary/native-app.json | 3 +- .../gothamadstest/exemplary/native-web.json | 3 +- .../gothamadstest/exemplary/video-app.json | 3 +- .../gothamadstest/exemplary/video-web.json | 3 +- .../supplemental/bad_media_type.json | 3 +- .../supplemental/empty-seatbid-array.json | 3 +- .../invalid-bidder-ext-object.json | 2 +- .../invalid-gotham-ext-object.json | 2 +- .../supplemental/invalid-response.json | 3 +- .../supplemental/status-code-bad-request.json | 3 +- .../supplemental/status-code-no-content.json | 3 +- .../supplemental/status-code-other-error.json | 3 +- .../status-code-service-unavailable.json | 3 +- adapters/gothamads/params_test.go | 2 +- adapters/grid/grid.go | 40 +- adapters/grid/grid_test.go | 6 +- .../gridtest/exemplary/multitype-banner.json | 3 +- .../gridtest/exemplary/multitype-native.json | 3 +- .../gridtest/exemplary/multitype-video.json | 3 +- .../gridtest/exemplary/native-as-string.json | 3 +- .../gridtest/exemplary/simple-banner.json | 3 +- .../gridtest/exemplary/simple-native.json | 3 +- .../grid/gridtest/exemplary/simple-video.json | 3 +- .../gridtest/exemplary/with-ext-keywords.json | 3 +- .../gridtest/exemplary/with-keywords.json | 3 +- .../exemplary/with-mixed-keywords.json | 3 +- .../exemplary/with-site-keywords.json | 3 +- .../exemplary/with-siteuser-keywords.json | 3 +- .../exemplary/with-user-keywords.json | 3 +- .../grid/gridtest/exemplary/with_gpid.json | 3 +- .../supplemental/bad_bidder_request.json | 2 +- .../supplemental/bad_ext_request.json | 2 +- .../gridtest/supplemental/bad_response.json | 5 +- .../gridtest/supplemental/status_204.json | 3 +- .../gridtest/supplemental/status_400.json | 3 +- .../gridtest/supplemental/status_418.json | 3 +- adapters/gumgum/gumgum.go | 55 +- adapters/gumgum/gumgum_test.go | 6 +- .../gumgum/gumgumtest/exemplary/banner.json | 3 +- .../gumgum/gumgumtest/exemplary/video.json | 3 +- .../supplemental/banner-only-in-format.json | 3 +- .../banner-with-pubId-product-params.json | 105 + .../supplemental/banner-with-pubId.json | 3 +- .../supplemental/banner-with-site.json | 3 +- .../supplemental/banner-with-slot-param.json | 3 +- .../supplemental/convert-currency.json | 111 + .../supplemental/missing-video-params.json | 36 - .../supplemental/video-partial-size.json | 105 + .../supplemental/video-with-irisid.json | 3 +- .../supplemental/video-zero-size.json | 107 + adapters/gumgum/params_test.go | 7 +- adapters/huaweiads/huaweiads.go | 158 +- adapters/huaweiads/huaweiads_test.go | 6 +- .../huaweiadstest/exemplary/banner1.json | 3 +- .../exemplary/banner1_without_userext.json | 3 +- .../huaweiadstest/exemplary/banner2.json | 3 +- .../huaweiadstest/exemplary/banner3.json | 3 +- .../exemplary/banner4_mccmnc.json | 3 +- .../exemplary/banner5_user_geo.json | 3 +- .../huaweiadstest/exemplary/banner6_imei.json | 3 +- .../exemplary/bannerAppPromotionType.json | 3 +- .../exemplary/bannerNonIntegerMccmnc.json | 3 +- .../exemplary/bannerNotAppPromotionType.json | 3 +- .../exemplary/bannerTestExtraInfo1.json | 3 +- .../exemplary/bannerTestExtraInfo2.json | 3 +- .../exemplary/bannerTestExtraInfo3.json | 3 +- .../exemplary/bannerWrongMccmnc.json | 3 +- .../exemplary/interstitialBannerType.json | 3 +- .../exemplary/interstitialVideoType.json | 3 +- .../exemplary/nativeIncludeVideo.json | 26 +- .../exemplary/nativeMultiSizesByRange.json | 307 ++ .../exemplary/nativeMultiSizesByRatio.json | 307 ++ .../exemplary/nativeSingleImage.json | 20 +- .../exemplary/nativeThreeImage.json | 25 +- .../nativeThreeImageIncludeIcon.json | 14 +- .../exemplary/rewardedVideo.json | 3 +- .../exemplary/rewardedVideo1.json | 3 +- .../exemplary/rewardedVideo2.json | 3 +- .../exemplary/rewardedVideo3.json | 3 +- .../exemplary/rewardedVideo4.json | 3 +- .../huaweiadstest/exemplary/rollVideo.json | 3 +- .../huaweiadstest/exemplary/video.json | 3 +- .../supplemental/bad_response.json | 3 +- .../supplemental/bad_response_400.json | 3 +- .../supplemental/bad_response_503.json | 3 +- .../bad_response_dont_find_impid.json | 3 +- .../bad_response_incorrect_huawei_adtype.json | 3 +- .../supplemental/bad_response_not_intent.json | 3 +- .../supplemental/bad_response_not_native.json | 43 +- .../bad_response_retcode30_204.json | 3 +- .../bad_response_retcode_210.json | 3 +- .../bad_response_retcode_408.json | 3 +- .../bad_response_retcode_500.json | 3 +- .../supplemental/missing_deviceid2.json | 2 +- .../supplemental/missing_video_size.json | 337 ++ .../supplemental/zero_video_size.json | 339 ++ adapters/huaweiads/params_test.go | 3 +- adapters/imds/imds.go | 24 +- adapters/imds/imds_test.go | 6 +- .../imdstest/exemplary/simple-banner.json | 3 +- .../imds/imdstest/exemplary/simple-video.json | 3 +- .../imdstest/supplemental/audio_response.json | 3 +- .../imdstest/supplemental/bad_response.json | 5 +- .../imdstest/supplemental/bad_seat_id.json | 2 +- .../supplemental/native_response.json | 3 +- .../imdstest/supplemental/one_bad_ext.json | 3 +- .../imdstest/supplemental/status_204.json | 3 +- .../imdstest/supplemental/status_400.json | 3 +- .../imdstest/supplemental/status_500.json | 3 +- adapters/imds/params_test.go | 2 +- adapters/impactify/impactify.go | 16 +- adapters/impactify/impactify_test.go | 6 +- .../exemplary/sample_banner.json | 3 +- .../impactifytest/exemplary/sample_video.json | 3 +- .../supplemental/bad_response.json | 3 +- .../impactifytest/supplemental/buyeruid.json | 3 +- .../impactifytest/supplemental/headers.json | 3 +- .../supplemental/headers_ip.json | 3 +- .../impactifytest/supplemental/http_204.json | 3 +- .../impactifytest/supplemental/http_400.json | 3 +- .../impactifytest/supplemental/http_500.json | 3 +- .../supplemental/negative_price.json | 3 +- .../supplemental/no_seat_bid.json | 3 +- .../not_supported_media_type.json | 3 +- .../impactifytest/supplemental/referer.json | 3 +- adapters/impactify/params_test.go | 2 +- adapters/improvedigital/improvedigital.go | 102 +- .../improvedigital/improvedigital_test.go | 6 +- .../exemplary/app-multi.json | 9 +- .../improvedigitaltest/exemplary/audio.json | 3 +- .../improvedigitaltest/exemplary/native.json | 3 +- .../exemplary/simple-banner.json | 3 +- .../exemplary/site-multi.json | 9 +- .../supplemental/ad-server-url.json | 3 +- .../supplemental/addtl-consent.json | 94 - .../supplemental/bad_response.json | 5 +- .../supplemental/dealid.json | 15 +- .../supplemental/foreign-currency.json | 3 +- .../missing_and_unsupported_mtype.json | 9 +- .../supplemental/multi-seatbid.json | 3 +- .../supplemental/nobid-debug.json | 3 +- .../supplemental/nobid.json | 3 +- .../supplemental/noseatbid.json | 3 +- .../supplemental/rewarded-inventory.json | 9 +- .../supplemental/status_204.json | 3 +- .../supplemental/status_400.json | 3 +- .../supplemental/status_418.json | 3 +- .../supplemental/wrong_impid.json | 3 +- adapters/improvedigital/params_test.go | 6 +- adapters/infoawarebidder.go | 8 +- adapters/infoawarebidder_test.go | 10 +- adapters/infytv/infytv.go | 14 +- adapters/infytv/infytv_test.go | 6 +- adapters/infytv/infytvtest/exemplary/app.json | 3 +- .../infytv/infytvtest/exemplary/video.json | 3 +- .../infytvtest/supplemental/bad-response.json | 5 +- .../supplemental/empty-seatbid.json | 3 +- .../infytvtest/supplemental/status-204.json | 3 +- .../infytvtest/supplemental/status-400.json | 3 +- .../infytvtest/supplemental/status-503.json | 3 +- .../supplemental/unexpected-status.json | 3 +- adapters/infytv/params_test.go | 2 +- adapters/inmobi/inmobi.go | 46 +- adapters/inmobi/inmobi_test.go | 6 +- .../exemplary/simple-app-banner.json | 7 +- .../exemplary/simple-app-native.json | 7 +- .../exemplary/simple-app-video.json | 7 +- .../exemplary/simple-web-banner.json | 7 +- .../exemplary/simple-web-native.json | 106 + .../exemplary/simple-web-video.json | 7 +- .../inmobi/inmobitest/supplemental/204.json | 3 +- .../inmobi/inmobitest/supplemental/400.json | 3 +- .../supplemental/banner-format-coersion.json | 7 +- .../supplemental/invalid-mtype.json | 97 + .../interactiveoffers/interactiveoffers.go | 20 +- .../interactiveoffers_test.go | 6 +- .../exemplary/goodmultiplebidrequest.json | 3 +- .../exemplary/goodsinglebidrequest.json | 3 +- .../supplemental/204.json | 3 +- .../supplemental/400.json | 3 +- .../supplemental/not200.json | 3 +- .../supplemental/wrongjsonresponse.json | 5 +- adapters/interactiveoffers/params_test.go | 3 +- adapters/invibes/invibes.go | 34 +- adapters/invibes/invibes_test.go | 6 +- adapters/invibes/invibestest/amp/amp-ad.json | 3 +- .../invibestest/exemplary/advanced-ad.json | 3 +- .../invibestest/exemplary/basic-ad.json | 3 +- .../invibes/invibestest/exemplary/no-ad.json | 3 +- .../invibestest/exemplary/test-ad.json | 3 +- .../supplemental/request-error-servererr.json | 3 +- .../request-error-statuscode.json | 3 +- adapters/invibes/params_test.go | 2 +- adapters/iqx/iqx.go | 20 +- adapters/iqx/iqxtest_test.go | 6 +- .../iqx/iqzonextest/exemplary/banner.json | 6 +- .../iqx/iqzonextest/exemplary/native.json | 3 +- adapters/iqx/iqzonextest/exemplary/video.json | 3 +- .../supplemental/bad-response.json | 5 +- .../supplemental/empty-mediatype.json | 3 +- .../supplemental/empty-seatbid-0-bid.json | 3 +- .../supplemental/empty-seatbid.json | 3 +- .../invalid-ext-bidder-object.json | 2 +- .../supplemental/invalid-ext-object.json | 2 +- .../supplemental/invalid-mediatype.json | 3 +- .../iqzonextest/supplemental/status-204.json | 3 +- .../iqzonextest/supplemental/status-400.json | 3 +- .../iqzonextest/supplemental/status-503.json | 3 +- .../supplemental/unexpected-status.json | 3 +- adapters/iqx/params_test.go | 2 +- adapters/iqzone/iqzone.go | 47 +- adapters/iqzone/iqzone_test.go | 6 +- .../iqzonetest/exemplary/endpointId.json | 5 +- .../iqzonetest/exemplary/simple-banner.json | 5 +- .../iqzonetest/exemplary/simple-native.json | 7 +- .../iqzonetest/exemplary/simple-video.json | 5 +- .../exemplary/simple-web-banner.json | 5 +- .../supplemental/bad_media_type.json | 5 +- .../iqzonetest/supplemental/bad_response.json | 5 +- .../iqzonetest/supplemental/status-204.json | 3 +- .../supplemental/status-not-200.json | 3 +- adapters/iqzone/params_test.go | 2 +- adapters/ix/ix.go | 94 +- adapters/ix/ix_test.go | 159 +- .../ixtest/exemplary/additional-consent.json | 3 +- adapters/ix/ixtest/exemplary/app-site-id.json | 3 +- .../ix/ixtest/exemplary/banner-no-format.json | 3 +- adapters/ix/ixtest/exemplary/fledge.json | 172 + .../multi-format-with-ext-prebid-type.json | 3 +- .../exemplary/multi-format-with-mtype.json | 3 +- .../multi-imp-multi-size-requests.json | 3 +- .../ixtest/exemplary/multi-imp-requests.json | 3 +- adapters/ix/ixtest/exemplary/multibid.json | 3 +- .../ix/ixtest/exemplary/multiple-siteIds.json | 3 +- .../native-eventtrackers-compat-12.json | 3 +- adapters/ix/ixtest/exemplary/no-pub-id.json | 3 +- adapters/ix/ixtest/exemplary/no-pub.json | 3 +- .../ix/ixtest/exemplary/simple-audio.json | 3 +- .../exemplary/simple-banner-multi-size.json | 3 +- .../ix/ixtest/exemplary/simple-native.json | 3 +- .../ix/ixtest/exemplary/simple-video.json | 3 +- .../ix/ixtest/exemplary/structured-pod.json | 3 +- .../supplemental/app-site-id-publisher.json | 3 +- .../ixtest/supplemental/bad-ext-bidder.json | 2 +- .../ix/ixtest/supplemental/bad-ext-ix.json | 4 +- .../ix/ixtest/supplemental/bad-fledge.json | 135 + .../ix/ixtest/supplemental/bad-imp-id.json | 3 +- .../ix/ixtest/supplemental/bad-request.json | 3 +- .../supplemental/bad-response-body.json | 5 +- .../ix/ixtest/supplemental/dsa-request.json | 194 ++ .../ix/ixtest/supplemental/fledge-no-bid.json | 138 + .../multi-imp-requests-error.json | 5 +- .../native-eventtrackers-empty.json | 3 +- .../native-eventtrackers-missing.json | 3 +- .../ixtest/supplemental/native-missing.json | 3 +- .../ix/ixtest/supplemental/no-content.json | 3 +- .../ix/ixtest/supplemental/not-found.json | 3 +- adapters/ix/ixtest/supplemental/sid.json | 3 +- adapters/ix/params_test.go | 2 +- adapters/jixie/jixie.go | 14 +- adapters/jixie/jixie_test.go | 6 +- .../exemplary/banner-and-video-site.json | 3 +- .../jixietest/supplemental/add-accountid.json | 3 +- .../jixietest/supplemental/add-extraprop.json | 3 +- .../jixietest/supplemental/add-userid.json | 3 +- adapters/jixie/params_test.go | 2 +- adapters/kargo/kargo.go | 16 +- adapters/kargo/kargo_test.go | 6 +- .../kargo/kargotest/exemplary/banner.json | 3 +- .../kargo/kargotest/exemplary/native.json | 3 +- adapters/kargo/kargotest/exemplary/video.json | 3 +- .../supplemental/status-bad-request.json | 9 +- .../supplemental/status-no-content.json | 3 +- adapters/kargo/params_test.go | 2 +- adapters/kayzen/kayzen.go | 20 +- adapters/kayzen/kayzen_test.go | 6 +- .../kayzentest/exemplary/banner-app.json | 3 +- .../kayzentest/exemplary/banner-web.json | 3 +- .../kayzentest/exemplary/native-app.json | 3 +- .../kayzentest/exemplary/native-web.json | 3 +- .../kayzentest/exemplary/video-app.json | 3 +- .../kayzentest/exemplary/video-web.json | 3 +- .../supplemental/invalid-response.json | 3 +- .../supplemental/status-code-bad-request.json | 3 +- .../supplemental/status-code-no-content.json | 3 +- .../supplemental/status-code-other-error.json | 3 +- adapters/kayzen/params_test.go | 2 +- adapters/kidoz/kidoz.go | 18 +- adapters/kidoz/kidoz_test.go | 12 +- .../kidoztest/exemplary/simple-banner.json | 3 +- .../kidoztest/exemplary/simple-video.json | 3 +- .../kidoz/kidoztest/supplemental/bad-bid.json | 3 +- .../supplemental/bidder-marshal.json | 2 +- .../kidoztest/supplemental/ext-marshal.json | 2 +- .../kidoztest/supplemental/status-204.json | 3 +- .../kidoztest/supplemental/status-400.json | 3 +- .../kidoztest/supplemental/status-403.json | 3 +- .../kidoztest/supplemental/status-408.json | 3 +- .../kidoztest/supplemental/status-500.json | 3 +- .../kidoztest/supplemental/status-502.json | 3 +- .../kidoztest/supplemental/status-503.json | 3 +- .../kidoztest/supplemental/status-504.json | 3 +- adapters/kidoz/params_test.go | 2 +- adapters/kiviads/kiviads.go | 18 +- adapters/kiviads/kiviads_test.go | 6 +- .../kiviadstest/exemplary/endpointId.json | 3 +- .../kiviadstest/exemplary/simple-banner.json | 3 +- .../kiviadstest/exemplary/simple-native.json | 3 +- .../kiviadstest/exemplary/simple-video.json | 3 +- .../exemplary/simple-web-banner.json | 3 +- .../supplemental/bad_media_type.json | 3 +- .../supplemental/bad_response.json | 5 +- .../kiviadstest/supplemental/status-204.json | 3 +- .../supplemental/status-not-200.json | 3 +- adapters/kiviads/params_test.go | 2 +- adapters/krushmedia/krushmedia.go | 20 +- adapters/krushmedia/krushmedia_test.go | 6 +- .../krushmediatest/exemplary/banner-app.json | 3 +- .../krushmediatest/exemplary/banner-web.json | 3 +- .../krushmediatest/exemplary/native-app.json | 3 +- .../krushmediatest/exemplary/native-web.json | 3 +- .../krushmediatest/exemplary/video-app.json | 3 +- .../krushmediatest/exemplary/video-web.json | 3 +- .../supplemental/invalid-response.json | 3 +- .../supplemental/status-code-bad-request.json | 3 +- .../supplemental/status-code-no-content.json | 3 +- .../supplemental/status-code-other-error.json | 3 +- .../status-code-service-unavailable.json | 3 +- adapters/krushmedia/params_test.go | 2 +- adapters/lemmadigital/lemmadigital.go | 27 +- adapters/lemmadigital/lemmadigital_test.go | 8 +- .../lemmadigitaltest/exemplary/banner.json | 5 +- .../lemmadigitaltest/exemplary/multi-imp.json | 5 +- .../lemmadigitaltest/exemplary/video.json | 5 +- .../supplemental/empty-seatbid-array.json | 5 +- .../invalid-ld-ext-bidder-object.json | 4 +- .../supplemental/invalid-ld-ext-object.json | 4 +- .../supplemental/invalid-response.json | 7 +- .../supplemental/status-code-bad-request.json | 5 +- .../supplemental/status-code-no-content.json | 5 +- .../supplemental/status-code-other-error.json | 5 +- adapters/lemmadigital/params_test.go | 2 +- adapters/liftoff/liftoff_test.go | 21 - .../LimelightDigitaltest/exemplary/audio.json | 3 +- .../exemplary/banner.json | 3 +- .../exemplary/multibid.json | 6 +- .../exemplary/native.json | 3 +- .../LimelightDigitaltest/exemplary/video.json | 3 +- .../supplemental/204.json | 3 +- .../supplemental/503.json | 3 +- .../supplemental/additional_fields.json | 3 +- .../supplemental/currency_converter.json | 3 +- .../supplemental/empty_object_response.json | 3 +- .../supplemental/empty_reponse.json | 3 +- .../supplemental/publisher_id_string.json | 3 +- .../supplemental/unknown_bid_impid.json | 3 +- .../supplemental/unknown_imp_media_type.json | 3 +- adapters/limelightDigital/limelightDigital.go | 22 +- .../limelightDigital/limelightDigital_test.go | 9 +- adapters/limelightDigital/params_test.go | 2 +- adapters/lm_kiviads/lmkiviads.go | 22 +- adapters/lm_kiviads/lmkiviads_test.go | 6 +- .../lmkiviadstest/exemplary/banner.json | 6 +- .../lmkiviadstest/exemplary/native.json | 3 +- .../lmkiviadstest/exemplary/video.json | 3 +- .../supplemental/bad-response.json | 5 +- .../supplemental/empty-mediatype.json | 3 +- .../supplemental/empty-seatbid-0-bid.json | 3 +- .../supplemental/empty-seatbid.json | 3 +- .../invalid-ext-bidder-object.json | 2 +- .../supplemental/invalid-ext-object.json | 2 +- .../supplemental/invalid-mediatype.json | 3 +- .../supplemental/status-204.json | 3 +- .../supplemental/status-400.json | 3 +- .../supplemental/status-503.json | 3 +- .../supplemental/unexpected-status.json | 3 +- adapters/lm_kiviads/params_test.go | 2 +- adapters/lockerdome/lockerdome.go | 18 +- adapters/lockerdome/lockerdome_test.go | 6 +- .../exemplary/simple-banner-multiple.json | 3 +- .../exemplary/simple-banner.json | 3 +- .../supplemental/bad_response.json | 5 +- .../supplemental/empty_seatbid.json | 3 +- .../supplemental/status_204.json | 3 +- .../supplemental/status_400.json | 3 +- .../supplemental/status_418.json | 3 +- adapters/lockerdome/params_test.go | 2 +- adapters/logan/logan.go | 16 +- adapters/logan/logan_test.go | 6 +- .../logantest/exemplary/simple-banner.json | 3 +- .../logantest/exemplary/simple-native.json | 3 +- .../logantest/exemplary/simple-video.json | 3 +- .../exemplary/simple-web-banner.json | 3 +- .../supplemental/bad_media_type.json | 3 +- .../logantest/supplemental/bad_response.json | 5 +- .../logantest/supplemental/status-204.json | 3 +- .../supplemental/status-not-200.json | 3 +- adapters/logan/params_test.go | 2 +- adapters/logicad/logicad.go | 20 +- adapters/logicad/logicad_test.go | 6 +- .../logicad/logicadtest/exemplary/banner.json | 3 +- .../logicad/logicadtest/supplemental/ext.json | 4 +- .../supplemental/multiImpSameTid.json | 3 +- .../supplemental/responseCode.json | 3 +- .../supplemental/responseNoBid.json | 3 +- .../logicadtest/supplemental/responsebid.json | 3 +- .../logicadtest/supplemental/site.json | 3 +- adapters/logicad/params_test.go | 3 +- adapters/loyal/loyal.go | 165 + adapters/loyal/loyal_test.go | 20 + .../loyal/loyaltest/exemplary/endpointId.json | 134 + .../loyaltest/exemplary/multi-format.json | 105 + .../loyal/loyaltest/exemplary/multi-imp.json | 249 ++ .../loyaltest/exemplary/simple-banner.json | 134 + .../loyaltest/exemplary/simple-native.json | 118 + .../loyaltest/exemplary/simple-video.json | 129 + .../exemplary/simple-web-banner.json | 134 + .../supplemental/bad_media_type.json | 83 + .../loyaltest/supplemental/bad_response.json | 85 + .../supplemental/no-valid-impressions.json | 20 + .../loyaltest/supplemental/status-204.json | 80 + .../supplemental/status-not-200.json | 85 + adapters/loyal/params_test.go | 47 + adapters/lunamedia/lunamedia.go | 22 +- adapters/lunamedia/lunamedia_test.go | 6 +- .../lunamediatest/exemplary/banner.json | 3 +- .../lunamediatest/exemplary/video.json | 3 +- .../lunamediatest/supplemental/compat.json | 3 +- .../lunamediatest/supplemental/ext.json | 4 +- .../supplemental/responseCode.json | 3 +- .../supplemental/responsebid.json | 3 +- .../lunamediatest/supplemental/site.json | 3 +- adapters/lunamedia/params_test.go | 3 +- adapters/mabidder/mabidder.go | 12 +- adapters/mabidder/mabidder_test.go | 6 +- .../exemplary/simple-app-banner.json | 3 +- .../supplemental/bad-request-example.json | 3 +- .../supplemental/bad-response-malformed.json | 7 +- .../supplemental/bad-response-status-500.json | 3 +- .../supplemental/no-content-response.json | 3 +- adapters/mabidder/params_test.go | 2 +- adapters/madvertise/madvertise.go | 22 +- adapters/madvertise/madvertise_test.go | 6 +- .../exemplary/simple-banner.json | 3 +- .../exemplary/simple-video.json | 4 +- .../supplemental/display-site-test.json | 3 +- .../supplemental/required-ext.json | 4 +- .../supplemental/response-204.json | 3 +- .../supplemental/response-400.json | 3 +- .../supplemental/response-500.json | 3 +- adapters/madvertise/params_test.go | 2 +- adapters/marsmedia/marsmedia.go | 18 +- adapters/marsmedia/marsmedia_test.go | 6 +- .../exemplary/simple-banner-int-param.json | 3 +- .../exemplary/simple-banner.json | 3 +- .../marsmediatest/exemplary/simple-video.json | 3 +- .../exemplary/valid-extension.json | 3 +- .../supplemental/missing-param.json | 2 +- adapters/marsmedia/params_test.go | 2 +- adapters/mediago/mediago.go | 211 ++ adapters/mediago/mediago_test.go | 28 + .../exemplary/sample-banner-apac.json | 114 + .../exemplary/sample-banner-euc.json | 114 + ...anner-fallback-to-first-imp-to-get-ep.json | 94 + .../exemplary/sample-banner-use.json | 114 + .../mediagotest/exemplary/sample-banner.json | 112 + .../mediagotest/exemplary/sample-native.json | 91 + .../mediagotest/exemplary/sample-nobid.json | 62 + .../exemplary/sample-with-mtype.json | 152 + .../supplemental/bad_400_reponse.json | 65 + .../supplemental/bad_500_reponse.json | 66 + .../mediagotest/supplemental/bad_imp_ext.json | 25 + .../supplemental/bad_impext_bidder.json | 27 + .../supplemental/bad_request_no_token.json | 30 + .../supplemental/bad_response.json | 66 + .../bad_response_error_mtype.json | 134 + adapters/mediago/params_test.go | 50 + adapters/medianet/medianet.go | 39 +- adapters/medianet/medianet_test.go | 6 +- .../medianettest/exemplary/multi-format.json | 3 +- .../medianettest/exemplary/multi-imps.json | 9 +- .../medianettest/exemplary/no-bid.json | 3 +- .../exemplary/optional-params.json | 3 +- .../medianettest/exemplary/simple-banner.json | 9 +- .../medianettest/exemplary/simple-video.json | 9 +- ...valid-req-400-status-code-bad-request.json | 3 +- .../valid-req-200-bid-response-from-mnet.json | 17 +- .../valid-req-204-response-from-mnet.json | 3 +- adapters/medianet/params_test.go | 2 +- adapters/melozen/melozen.go | 186 ++ adapters/melozen/melozen_test.go | 30 + .../melozentest/exemplary/app-banner.json | 132 + .../melozentest/exemplary/app-native.json | 100 + .../melozentest/exemplary/app-video.json | 137 + .../melozentest/exemplary/multi-imps.json | 239 ++ .../melozentest/exemplary/web-banner.json | 138 + .../melozentest/exemplary/web-video.json | 129 + .../supplemental/bad-media-type-request.json | 28 + .../melozentest/supplemental/no-fill.json | 90 + .../supplemental/response-status-400.json | 95 + .../supplemental/response-status-not-200.json | 84 + .../supplemental/wrong-bid-ext.json | 85 + adapters/melozen/params_test.go | 50 + adapters/metax/metax.go | 195 ++ adapters/metax/metax_test.go | 214 ++ .../metaxtest/exemplary/app-formats.json | 156 + .../metax/metaxtest/exemplary/app-imps.json | 351 ++ .../metax/metaxtest/exemplary/no-bid.json | 92 + .../metaxtest/exemplary/no-seat-bid.json | 99 + .../metax/metaxtest/exemplary/no-seat.json | 94 + .../metaxtest/exemplary/simple-app-audio.json | 110 + .../exemplary/simple-app-banner.json | 122 + .../exemplary/simple-app-native.json | 99 + .../metaxtest/exemplary/simple-app-video.json | 130 + .../exemplary/simple-site-audio.json | 110 + .../exemplary/simple-site-banner.json | 122 + .../exemplary/simple-site-native.json | 99 + .../exemplary/simple-site-video.json | 130 + .../supplemental/invalid-adunit-error.json | 35 + .../supplemental/invalid-ext-bidder.json | 32 + .../metaxtest/supplemental/invalid-ext.json | 30 + .../supplemental/invalid-publisher-error.json | 35 + .../metaxtest/supplemental/resp-bad-json.json | 80 + .../supplemental/resp-bad-markuptype.json | 100 + .../metaxtest/supplemental/status-400.json | 80 + .../metaxtest/supplemental/status-500.json | 80 + .../metaxtest/supplemental/status-503.json | 80 + adapters/metax/params_test.go | 60 + adapters/mgid/mgid.go | 20 +- adapters/mgid/mgid_test.go | 6 +- .../mgidtest/exemplary/noplacementid.json | 3 +- .../mgidtest/exemplary/simple-banner.json | 3 +- .../exemplary/simple-banner_no_device.json | 3 +- .../simple-banner_no_device_no_site.json | 3 +- .../exemplary/simple-banner_with_crtype.json | 3 +- .../mgidtest/supplemental/status_204.json | 3 +- .../mgidtest/supplemental/status_not200.json | 3 +- .../mgid/mgidtest/supplemental/video.json | 3 +- adapters/mgidX/mgidX.go | 18 +- adapters/mgidX/mgidX_test.go | 6 +- .../mgidX/mgidXtest/exemplary/endpointId.json | 3 +- .../mgidXtest/exemplary/simple-banner.json | 3 +- .../mgidXtest/exemplary/simple-native.json | 3 +- .../mgidXtest/exemplary/simple-video.json | 3 +- .../exemplary/simple-web-banner.json | 3 +- .../supplemental/bad_media_type.json | 3 +- .../mgidXtest/supplemental/bad_response.json | 5 +- .../mgidXtest/supplemental/status-204.json | 3 +- .../supplemental/status-not-200.json | 3 +- adapters/mgidX/params_test.go | 2 +- adapters/minutemedia/minutemedia.go | 123 + adapters/minutemedia/minutemedia_test.go | 24 + .../exemplary/banner-and-video-app.json | 252 ++ .../exemplary/banner-and-video-gdpr.json | 216 ++ .../exemplary/banner-and-video-site.json | 234 ++ .../exemplary/banner-and-video.json | 194 ++ .../exemplary/simple-banner.json | 118 + .../exemplary/simple-video.json | 139 + .../supplemental/bad-request.json | 106 + .../supplemental/missing-bidder.json | 48 + .../supplemental/missing-extension.json | 47 + .../supplemental/missing-mtype.json | 150 + adapters/missena/missena.go | 216 ++ adapters/missena/missena_test.go | 21 + .../missenatest/exemplary/multiple-imps.json | 129 + .../exemplary/simple-banner-ipv6.json | 105 + .../missenatest/exemplary/simple-banner.json | 105 + .../exemplary/valid-imp-error-imp.json | 129 + .../supplemental/error-ext-bidder.json | 25 + .../supplemental/error-imp-ext.json | 23 + .../missenatest/supplemental/status-204.json | 83 + .../missenatest/supplemental/status-400.json | 89 + .../supplemental/status-not-200.json | 89 + adapters/missena/params_test.go | 50 + adapters/mobfoxpb/mobfoxpb.go | 16 +- adapters/mobfoxpb/mobfoxpb_test.go | 6 +- .../exemplary/simple-banner-direct-route.json | 3 +- .../exemplary/simple-banner-rtb-route.json | 3 +- .../exemplary/simple-video-direct-route.json | 3 +- .../exemplary/simple-video-rtb-route.json | 3 +- .../simple-web-banner-direct-route.json | 3 +- .../simple-web-banner-rtb-route.json | 3 +- .../supplemental/bad-imp-ext.json | 2 +- .../supplemental/bad_response.json | 5 +- .../supplemental/bad_status_code.json | 3 +- .../supplemental/imp_ext_empty_object.json | 2 +- .../supplemental/imp_ext_string.json | 2 +- .../supplemental/missmatch_bid_id.json | 3 +- .../mobfoxpbtest/supplemental/status-204.json | 3 +- .../mobfoxpbtest/supplemental/status-404.json | 3 +- adapters/mobfoxpb/params_test.go | 2 +- adapters/mobilefuse/mobilefuse.go | 28 +- adapters/mobilefuse/mobilefuse_test.go | 6 +- .../exemplary/multi-format.json | 3 +- .../exemplary/multi-imps-multi-format.json | 3 +- .../mobilefusetest/exemplary/multi-imps.json | 3 +- .../mobilefusetest/exemplary/no-bid.json | 3 +- .../exemplary/optional-params.json | 3 +- .../exemplary/simple-banner.json | 3 +- .../exemplary/simple-native.json | 3 +- .../exemplary/simple-video.json | 3 +- .../mobilefusetest/exemplary/skadn.json | 3 +- .../supplemental/bad-ext-bidder.json | 2 +- .../mobilefusetest/supplemental/bad-ext.json | 2 +- .../supplemental/bad-status-code.json | 3 +- .../supplemental/server-error-response.json | 3 +- adapters/mobilefuse/params_test.go | 2 +- adapters/motorik/motorik.go | 20 +- adapters/motorik/motorik_test.go | 6 +- .../motoriktest/exemplary/banner-app.json | 3 +- .../motoriktest/exemplary/banner-web.json | 3 +- .../motoriktest/exemplary/native-app.json | 3 +- .../motoriktest/exemplary/native-web.json | 3 +- .../motoriktest/exemplary/video-app.json | 3 +- .../motoriktest/exemplary/video-web.json | 3 +- .../supplemental/bad_media_type.json | 3 +- .../supplemental/empty-seatbid-array.json | 3 +- .../invalid-bidder-ext-object.json | 2 +- .../invalid-motorik-ext-object.json | 2 +- .../supplemental/invalid-response.json | 3 +- .../supplemental/status-code-bad-request.json | 3 +- .../supplemental/status-code-no-content.json | 3 +- .../supplemental/status-code-other-error.json | 3 +- .../status-code-service-unavailable.json | 3 +- adapters/motorik/params_test.go | 2 +- adapters/nativo/nativo.go | 96 + adapters/nativo/nativo_test.go | 21 + .../nativotest/exemplary/banner-app.json | 133 + .../nativotest/exemplary/banner-web.json | 121 + .../nativotest/exemplary/native-app.json | 130 + .../nativotest/exemplary/native-web.json | 117 + .../nativotest/exemplary/video-app.json | 143 + .../nativotest/exemplary/video-web.json | 141 + ...-different-impID-response-from-nativo.json | 81 + .../204-response-from-nativo.json | 53 + .../400-response-from-nativo.json | 58 + .../500-response-from-nativo.json | 58 + adapters/nextmillennium/nextmillennium.go | 86 +- .../nextmillennium/nextmillennium_test.go | 19 +- .../exemplary/banner-empty-group-id.json | 20 +- .../exemplary/banner-with-group-id.json | 20 +- .../exemplary/banner-with-group-id_app.json | 20 +- .../exemplary/banner-with-only-width.json | 20 +- .../exemplary/banner-with-wh.json | 20 +- .../exemplary/banner-wo-domain.json | 20 +- .../exemplary/banner-wo-size.json | 20 +- .../nextmillenniumtest/exemplary/banner.json | 20 +- .../exemplary/empty-banner-obj.json | 20 +- .../nextmillenniumtest/exemplary/video.json | 119 + .../supplemental/empty-seatbid.json | 10 +- .../supplemental/error-response.json | 10 +- .../nextmillenniumtest/supplemental/ext.json | 4 +- .../supplemental/no-content.json | 10 +- adapters/nextmillennium/params_test.go | 2 +- adapters/nobid/nobid.go | 16 +- adapters/nobid/nobid_test.go | 6 +- .../nobid/nobidtest/exemplary/banner.json | 3 +- .../nobidtest/supplemental/bad-mediatype.json | 3 +- .../nobidtest/supplemental/bad-request.json | 3 +- .../nobidtest/supplemental/bad-response.json | 3 +- .../nobidtest/supplemental/no-content.json | 3 +- .../supplemental/notok-response.json | 3 +- adapters/nobid/params_test.go | 2 +- adapters/oms/oms.go | 83 + adapters/oms/oms_test.go | 20 + .../exemplary/simple-banner-cookie-uid.json | 129 + .../simple-banner-multiple-bids.json | 217 ++ .../omstest/exemplary/simple-banner-uid.json | 150 + .../exemplary/simple-multi-type-banner.json | 162 + .../204-response-from-target.json | 86 + .../400-response-from-target.json | 91 + .../500-response-from-target.json | 91 + .../supplemental/simple-banner-with-ipv6.json | 143 + adapters/oms/params_test.go | 56 + adapters/onetag/onetag.go | 20 +- adapters/onetag/onetag_test.go | 6 +- .../onetag/onetagtest/exemplary/no-bid.json | 3 +- .../onetagtest/exemplary/simple-banner.json | 3 +- .../onetagtest/exemplary/simple-native.json | 3 +- .../onetagtest/exemplary/simple-video.json | 3 +- .../supplemental/internal-server-error.json | 3 +- .../supplemental/wrong-impression-id.json | 3 +- adapters/onetag/params_test.go | 2 +- adapters/openweb/openweb.go | 212 +- adapters/openweb/openweb_test.go | 8 +- .../exemplary/multiple-imps-same-aid.json | 174 - .../openwebtest/exemplary/simple-banner.json | 137 +- .../openwebtest/exemplary/simple-video.json | 24 +- .../supplemental/missing-mtype.json | 93 + ...lid-imps.json => missing-org-and-aid.json} | 6 +- .../supplemental/missing-placement-id.json | 29 + .../openwebtest/supplemental/status-500.json | 65 - adapters/openweb/params_test.go | 16 +- adapters/openx/openx.go | 49 +- adapters/openx/openx_test.go | 10 +- .../openxtest/exemplary/fledge-no-bids.json | 3 +- .../openx/openxtest/exemplary/fledge.json | 3 +- .../exemplary/imp-ext-passthrough.json | 3 +- .../openxtest/exemplary/optional-params.json | 9 +- .../openxtest/exemplary/simple-banner.json | 3 +- .../openxtest/exemplary/simple-video.json | 17 +- .../openxtest/exemplary/video-rewarded.json | 10 +- .../openxtest/supplemental/multi-imp.json | 29 +- adapters/openx/params_test.go | 10 +- adapters/operaads/operaads.go | 29 +- adapters/operaads/operaads_test.go | 6 +- .../operaadstest/exemplary/multiformat.json | 6 +- .../operaadstest/exemplary/native.json | 3 +- .../operaadstest/exemplary/simple-banner.json | 3 +- .../operaadstest/exemplary/video.json | 3 +- .../operaadstest/supplemental/badrequest.json | 3 +- .../supplemental/miss-native.json | 3 +- .../operaadstest/supplemental/nocontent.json | 3 +- .../supplemental/unexcept-statuscode.json | 3 +- adapters/operaads/params_test.go | 2 +- adapters/oraki/oraki.go | 153 + adapters/oraki/oraki_test.go | 20 + .../oraki/orakitest/exemplary/endpointId.json | 136 + .../orakitest/exemplary/multi-format.json | 105 + .../oraki/orakitest/exemplary/multi-imp.json | 253 ++ .../orakitest/exemplary/simple-banner.json | 136 + .../orakitest/exemplary/simple-native.json | 120 + .../orakitest/exemplary/simple-video.json | 131 + .../exemplary/simple-web-banner.json | 136 + .../supplemental/bad_media_type.json | 83 + .../orakitest/supplemental/bad_response.json | 85 + .../orakitest/supplemental/status-204.json | 80 + .../supplemental/status-not-200.json | 85 + adapters/oraki/params_test.go | 47 + adapters/orbidder/orbidder.go | 18 +- adapters/orbidder/orbidder_test.go | 10 +- .../multibid-multi-format-with-mtype.json | 3 +- .../exemplary/simple-app-banner.json | 3 +- .../exemplary/simple-web-banner.json | 3 +- .../exemplary/simple-web-native.json | 3 +- .../supplemental/dsp-bad-request-example.json | 3 +- .../dsp-bad-response-example.json | 3 +- .../dsp-internal-server-error-example.json | 3 +- .../dsp-invalid-accountid-example.json | 3 +- .../supplemental/ext-unmarshall-error.json | 2 +- .../supplemental/no-content-response.json | 3 +- .../supplemental/valid-and-invalid-imps.json | 7 +- adapters/orbidder/params_test.go | 2 +- adapters/outbrain/outbrain.go | 24 +- adapters/outbrain/outbrain_test.go | 6 +- .../outbraintest/exemplary/banner.json | 3 +- .../outbraintest/exemplary/native.json | 3 +- .../outbraintest/exemplary/video.json | 3 +- .../supplemental/app_request.json | 3 +- .../outbraintest/supplemental/eids.json | 3 +- .../supplemental/general_params.json | 3 +- .../supplemental/optional_params.json | 3 +- .../outbraintest/supplemental/status_204.json | 3 +- .../outbraintest/supplemental/status_400.json | 3 +- .../outbraintest/supplemental/status_418.json | 3 +- adapters/outbrain/params_test.go | 2 +- adapters/ownadx/ownadx.go | 38 +- adapters/ownadx/ownadx_test.go | 11 +- .../ownadx/ownadxtest/exemplary/banner.json | 3 +- .../ownadx/ownadxtest/exemplary/video.json | 3 +- .../supplemental/bad-server-response.json | 3 +- .../ownadxtest/supplemental/bid-empty-.json | 3 +- .../ownadxtest/supplemental/bidext-type.json | 3 +- .../supplemental/http-status-204.json | 3 +- .../supplemental/http-status-400.json | 3 +- .../supplemental/seatbid-empty-.json | 3 +- .../supplemental/unexpected-status.json | 3 +- adapters/pangle/pangle.go | 20 +- adapters/pangle/pangle_test.go | 6 +- .../pangletest/exemplary/app_banner.json | 3 +- .../exemplary/app_banner_instl.json | 3 +- .../pangletest/exemplary/app_native.json | 3 +- .../pangletest/exemplary/app_video_instl.json | 3 +- .../exemplary/app_video_rewarded.json | 3 +- .../supplemental/appid_placementid_check.json | 3 +- .../supplemental/pangle_ext_check.json | 3 +- .../supplemental/response_code_204.json | 3 +- .../supplemental/response_code_400.json | 3 +- .../supplemental/response_code_non_200.json | 3 +- .../supplemental/unrecognized_adtype.json | 3 +- adapters/pangle/param_test.go | 2 +- adapters/pgamssp/params_test.go | 2 +- adapters/pgamssp/pgamssp.go | 28 +- adapters/pgamssp/pgamssp_test.go | 6 +- .../exemplary/convert_currency.json | 154 + .../pgamssptest/exemplary/endpointId.json | 3 +- .../pgamssptest/exemplary/simple-banner.json | 3 +- .../pgamssptest/exemplary/simple-native.json | 3 +- .../pgamssptest/exemplary/simple-video.json | 3 +- .../exemplary/simple-web-banner.json | 3 +- .../supplemental/bad_media_type.json | 3 +- .../supplemental/bad_response.json | 5 +- .../pgamssptest/supplemental/status-204.json | 3 +- .../supplemental/status-not-200.json | 3 +- adapters/playdigo/params_test.go | 47 + adapters/playdigo/playdigo.go | 161 + adapters/playdigo/playdigo_test.go | 20 + .../playdigotest/exemplary/endpointId.json | 136 + .../playdigotest/exemplary/multi-format.json | 105 + .../playdigotest/exemplary/multi-imp.json | 253 ++ .../playdigotest/exemplary/simple-banner.json | 136 + .../playdigotest/exemplary/simple-native.json | 120 + .../playdigotest/exemplary/simple-video.json | 131 + .../exemplary/simple-web-banner.json | 136 + .../supplemental/bad_media_type.json | 83 + .../supplemental/bad_response.json | 85 + .../supplemental/no-valid-impressions.json | 20 + .../playdigotest/supplemental/status-204.json | 80 + .../supplemental/status-not-200.json | 85 + adapters/pubmatic/params_test.go | 2 +- adapters/pubmatic/pubmatic.go | 54 +- adapters/pubmatic/pubmatic_test.go | 10 +- .../pubmatictest/exemplary/banner.json | 3 +- .../pubmatictest/exemplary/fledge.json | 3 +- .../pubmatictest/exemplary/native.json | 3 +- .../pubmatictest/exemplary/video.json | 6 +- .../pubmatictest/supplemental/app.json | 7 +- .../supplemental/dctrAndPmZoneID.json | 3 +- .../pubmatictest/supplemental/extra-bid.json | 3 +- .../supplemental/gptSlotNameInImpExt.json | 3 +- .../gptSlotNameInImpExtPbAdslot.json | 3 +- .../{impExtData.json => impExt.json} | 9 +- .../supplemental/multiplemedia.json | 3 +- .../supplemental/native_invalid_adm.json | 3 +- .../pubmatictest/supplemental/nilReqExt.json | 3 +- .../pubmatictest/supplemental/noAdSlot.json | 3 +- .../supplemental/pmZoneIDInKeywords.json | 3 +- .../supplemental/reqBidderParams.json | 3 +- .../supplemental/trimPublisherID.json | 3 +- adapters/pubnative/pubnative.go | 23 +- adapters/pubnative/pubnative_test.go | 6 +- .../pubnativetest/exemplary/native.json | 3 +- .../exemplary/simple-banner.json | 3 +- .../pubnativetest/exemplary/video.json | 3 +- adapters/pubrise/params_test.go | 47 + adapters/pubrise/pubrise.go | 160 + adapters/pubrise/pubrise_test.go | 20 + .../pubrisetest/exemplary/endpointId.json | 136 + .../pubrisetest/exemplary/multi-format.json | 105 + .../pubrisetest/exemplary/multi-imp.json | 253 ++ .../pubrisetest/exemplary/simple-banner.json | 136 + .../pubrisetest/exemplary/simple-native.json | 120 + .../pubrisetest/exemplary/simple-video.json | 131 + .../exemplary/simple-web-banner.json | 136 + .../supplemental/bad_media_type.json | 83 + .../supplemental/bad_response.json | 85 + .../supplemental/no-valid-impressions.json | 20 + .../pubrisetest/supplemental/status-204.json | 80 + .../supplemental/status-not-200.json | 85 + adapters/pulsepoint/params_test.go | 9 +- adapters/pulsepoint/pulsepoint.go | 50 +- adapters/pulsepoint/pulsepoint_test.go | 6 +- .../pulsepointtest/exemplary/banner-app.json | 3 +- .../banner-string-bidder-params.json | 102 + .../pulsepointtest/exemplary/banner.json | 3 +- .../exemplary/empty-pub-node-app.json | 3 +- .../exemplary/empty-pub-node-site.json | 3 +- .../pulsepointtest/exemplary/multi-imps.json | 3 +- .../pulsepointtest/exemplary/native.json | 3 +- .../pulsepointtest/exemplary/video.json | 3 +- .../supplemental/bad-bid-data.json | 7 +- .../supplemental/bad-input.json | 3 +- .../bad-prebid-params-missing-param.json | 31 + .../supplemental/bad-prebid-params.json | 4 +- .../supplemental/bad-prebid.ext.json | 2 +- .../pulsepointtest/supplemental/error.json | 3 +- .../supplemental/impid-mismatch.json | 3 +- .../pulsepointtest/supplemental/passback.json | 3 +- adapters/pwbid/params_test.go | 2 +- adapters/pwbid/pwbid.go | 14 +- adapters/pwbid/pwbid_test.go | 6 +- .../pwbid/pwbidtest/exemplary/banner.json | 3 +- .../pwbid/pwbidtest/exemplary/native.json | 3 +- .../pwbidtest/exemplary/optional-params.json | 3 +- adapters/pwbid/pwbidtest/exemplary/video.json | 3 +- .../response-200-without-body.json | 7 +- .../pwbidtest/supplemental/response-204.json | 3 +- .../pwbidtest/supplemental/response-400.json | 3 +- .../pwbidtest/supplemental/response-500.json | 3 +- adapters/qt/params_test.go | 47 + adapters/qt/qt.go | 153 + adapters/qt/qt_test.go | 20 + adapters/qt/qttest/exemplary/endpointId.json | 136 + .../qt/qttest/exemplary/multi-format.json | 105 + adapters/qt/qttest/exemplary/multi-imp.json | 253 ++ .../qt/qttest/exemplary/simple-banner.json | 136 + .../qt/qttest/exemplary/simple-native.json | 120 + .../qt/qttest/exemplary/simple-video.json | 131 + .../qttest/exemplary/simple-web-banner.json | 136 + .../qttest/supplemental/bad_media_type.json | 83 + .../qt/qttest/supplemental/bad_response.json | 85 + .../qt/qttest/supplemental/status-204.json | 80 + .../qttest/supplemental/status-not-200.json | 85 + adapters/readpeak/params_test.go | 51 + adapters/readpeak/readpeak.go | 162 + adapters/readpeak/readpeak_test.go | 21 + .../readpeaktest/exemplary/banner.json | 130 + .../readpeaktest/exemplary/banner_app.json | 128 + .../exemplary/banner_multiple_imps.json | 200 ++ .../readpeaktest/exemplary/native.json | 118 + .../supplemental/banner_without_mtype.json | 115 + .../supplemental/bid_response_204.json | 72 + .../supplemental/bid_response_400.json | 100 + .../supplemental/bid_response_500.json | 100 + adapters/relevantdigital/params_test.go | 42 + adapters/relevantdigital/relevantdigital.go | 332 ++ .../relevantdigital/relevantdigital_test.go | 28 + .../exemplary/simple-audio.json | 117 + .../exemplary/simple-banner.json | 207 ++ .../exemplary/simple-native.json | 119 + .../exemplary/simple-video.json | 186 ++ .../supplemental/invalidBidMType.json | 100 + .../invalidBidMTypeParsesExt.json | 158 + .../supplemental/invalidBidType.json | 103 + .../supplemental/invalidParam.json | 42 + .../supplemental/invalidRequestCount.json | 39 + adapters/response.go | 2 +- adapters/response_test.go | 2 +- adapters/revcontent/revcontent.go | 14 +- adapters/revcontent/revcontent_test.go | 6 +- .../revcontenttest/exemplary/no-bid.json | 3 +- .../exemplary/simple-banner.json | 3 +- .../exemplary/simple-native.json | 3 +- .../supplemental/bad_response.json | 5 +- .../supplemental/status_400.json | 3 +- .../supplemental/status_500.json | 3 +- adapters/richaudience/params_test.go | 2 +- adapters/richaudience/richaudience.go | 26 +- adapters/richaudience/richaudience_test.go | 10 +- .../exemplary/single-banner-app.json | 3 +- .../single-banner-defaultCurrency.json | 3 +- .../exemplary/single-banner-deviceConfig.json | 3 +- .../exemplary/single-banner-extUser.json | 3 +- .../exemplary/single-banner-floorPrice.json | 3 +- .../exemplary/single-banner-iPv6.json | 3 +- .../exemplary/single-banner-nil-device.json | 3 +- .../exemplary/single-banner-nosecure.json | 3 +- .../exemplary/single-banner-setCurrency.json | 3 +- .../exemplary/single-banner-sitePage.json | 3 +- .../exemplary/single-banner.json | 3 +- .../exemplary/single-video.json | 3 +- .../supplemental/bidTypeNotAssigned.json | 3 +- .../supplemental/responseBlank.json | 3 +- .../supplemental/statusCode400.json | 3 +- .../supplemental/statusCodeError.json | 3 +- .../supplemental/unexpectedStatusCode.json | 3 +- .../supplemental/videoSizePartial.json | 42 + .../supplemental/videoSizeZero.json | 43 + adapters/rise/rise.go | 16 +- adapters/rise/rise_test.go | 6 +- .../exemplary/banner-and-video-app.json | 3 +- .../exemplary/banner-and-video-gdpr.json | 3 +- .../exemplary/banner-and-video-site.json | 3 +- .../risetest/exemplary/banner-and-video.json | 3 +- .../exemplary/simple-banner-both-ids.json | 3 +- .../risetest/exemplary/simple-banner.json | 3 +- .../rise/risetest/exemplary/simple-video.json | 3 +- .../risetest/supplemental/bad-request.json | 3 +- .../risetest/supplemental/missing-bidder.json | 4 +- .../supplemental/missing-extension.json | 4 +- .../risetest/supplemental/missing-mtype.json | 3 +- adapters/roulax/roulax.go | 118 + adapters/roulax/roulax_test.go | 24 + .../roulaxtest/exemplary/simple-banner.json | 116 + .../roulaxtest/exemplary/simple-native.json | 98 + .../roulaxtest/exemplary/simple-video.json | 122 + .../supplemental/no-bid-response.json | 80 + adapters/rtbhouse/rtbhouse.go | 95 +- adapters/rtbhouse/rtbhouse_test.go | 6 +- .../rtbhousetest/exemplary/app_banner.json | 99 + .../rtbhousetest/exemplary/app_native.json | 113 + .../exemplary/banner-resolve-macros.json | 87 + .../bidfloor-as-bidder-param-without-cur.json | 131 + .../exemplary/bidfloor-as-bidder-param.json | 134 + .../bidfloor-as-impbidfloor-with-cur.json | 131 + .../bidfloor-as-impbidfloor-without-cur.json | 129 + .../exemplary/currency-conversion.json | 123 - .../native-with-deprecated-native-prop.json | 101 + .../native-with-proper-native-response.json | 101 + .../rtbhousetest/exemplary/simple-banner.json | 9 +- ...bidfloors-given-param-and-impbidfloor.json | 133 + .../supplemental/bad_response.json | 5 +- ...ner-native-req-faulty-mtype-in-native.json | 142 + .../faulty-request-bidder-params.json | 30 + .../faulty-request-no-impext.json | 25 + .../native-with-faulty-adm-native-prop.json | 91 + .../native-with-faulty-adm-response.json | 91 + .../supplemental/simple-banner-bad-mtype.json | 95 + .../supplemental/simple-banner-no-mtype.json | 94 + .../rtbhousetest/supplemental/status_204.json | 3 +- .../rtbhousetest/supplemental/status_400.json | 3 +- .../rtbhousetest/supplemental/status_418.json | 3 +- adapters/rubicon/rubicon.go | 97 +- adapters/rubicon/rubicon_test.go | 112 +- .../exemplary/25-26-transition-period.json | 8 +- .../rubicontest/exemplary/app-imp-fpd.json | 8 +- .../exemplary/bidonmultiformat.json | 16 +- .../exemplary/flexible-schema.json | 8 +- .../exemplary/hardcode-secure.json | 8 +- .../exemplary/non-bidonmultiformat.json | 8 +- .../rubicontest/exemplary/simple-banner.json | 8 +- .../rubicontest/exemplary/simple-native.json | 8 +- .../rubicontest/exemplary/simple-video.json | 8 +- .../rubicontest/exemplary/site-imp-fpd.json | 8 +- .../rubicontest/exemplary/user-fpd.json | 8 +- .../supplemental/invalid-bidder-ext.json | 2 +- .../supplemental/no-site-content-data.json | 8 +- .../supplemental/no-site-content.json | 8 +- adapters/sa_lunamedia/params_test.go | 2 +- adapters/sa_lunamedia/salunamedia.go | 16 +- adapters/sa_lunamedia/salunamedia_test.go | 6 +- .../salunamediatest/exemplary/banner.json | 3 +- .../salunamediatest/exemplary/native.json | 3 +- .../salunamediatest/exemplary/video.json | 3 +- .../supplemental/bad-response.json | 5 +- .../supplemental/empty-seatbid.json | 3 +- .../supplemental/status-204.json | 3 +- .../supplemental/status-400.json | 3 +- .../supplemental/status-503.json | 3 +- .../supplemental/unexpected-status.json | 3 +- adapters/screencore/params_test.go | 2 +- adapters/screencore/screencore.go | 20 +- adapters/screencore/screencore_test.go | 6 +- .../screencoretest/exemplary/banner-app.json | 3 +- .../screencoretest/exemplary/banner-web.json | 3 +- .../screencoretest/exemplary/native-app.json | 3 +- .../screencoretest/exemplary/native-web.json | 3 +- .../screencoretest/exemplary/video-app.json | 3 +- .../screencoretest/exemplary/video-web.json | 3 +- .../supplemental/bad_media_type.json | 3 +- .../supplemental/empty-seatbid-array.json | 3 +- .../invalid-bidder-ext-object.json | 2 +- .../supplemental/invalid-response.json | 3 +- .../invalid-screencore-ext-object.json | 2 +- .../supplemental/status-code-bad-request.json | 3 +- .../supplemental/status-code-no-content.json | 3 +- .../supplemental/status-code-other-error.json | 3 +- .../status-code-service-unavailable.json | 3 +- adapters/seedingAlliance/params_test.go | 6 +- adapters/seedingAlliance/seedingAlliance.go | 63 +- .../seedingAlliance/seedingAlliance_test.go | 51 +- .../seedingAlliancetest/exemplary/banner.json | 5 +- .../exemplary/banner_with_account.json | 145 + .../banner_with_account_and_seat.json | 147 + .../exemplary/banner_with_seat.json} | 11 +- .../seedingAlliancetest/exemplary/native.json | 5 +- .../supplemental/invalid_tag_id.json | 2 +- .../supplemental/status_bad_request.json | 5 +- .../supplemental/status_no_content.json | 5 +- .../supplemental/status_not_ok.json | 5 +- adapters/sharethrough/params_test.go | 2 +- adapters/sharethrough/sharethrough.go | 26 +- adapters/sharethrough/sharethrough_test.go | 6 +- .../exemplary/app-banner.json | 3 +- .../exemplary/app-native.json | 3 +- .../sharethroughtest/exemplary/app-video.json | 3 +- .../exemplary/web-banner.json | 3 +- .../sharethroughtest/exemplary/web-video.json | 3 +- .../supplemental/error-bad-request.json | 3 +- .../supplemental/error-internal-server.json | 3 +- .../supplemental/multi-imp.json | 6 +- .../supplemental/multiformat-impression.json | 9 +- .../supplemental/no-fill.json | 3 +- .../sharethroughtest/supplemental/schain.json | 3 +- adapters/silvermob/params_test.go | 2 +- adapters/silvermob/silvermob.go | 20 +- adapters/silvermob/silvermob_test.go | 6 +- .../silvermobtest/exemplary/banner-app.json | 3 +- .../exemplary/banner-multi-app.json | 6 +- .../silvermobtest/exemplary/native-app.json | 3 +- .../silvermobtest/exemplary/video-app.json | 3 +- .../supplemental/empty-seatbid-array.json | 3 +- .../supplemental/invalid-response.json | 5 +- .../invalid-silvermob-ext-object.json | 2 +- .../supplemental/status-code-bad-request.json | 3 +- .../supplemental/status-code-no-content.json | 3 +- .../supplemental/status-code-other-error.json | 3 +- .../status-code-service-unavailable.json | 3 +- adapters/silverpush/devicetype.go | 2 +- adapters/silverpush/params_test.go | 2 +- adapters/silverpush/silverpush.go | 27 +- adapters/silverpush/silverpush_test.go | 10 +- .../exemplary/banner-bidfloor-zero.json | 3 +- .../exemplary/banner-device-ctv-.json | 3 +- .../exemplary/banner-device-site.json | 3 +- .../silverpushtest/exemplary/banner-eids.json | 3 +- .../exemplary/banner-iOS-ua.json | 3 +- .../exemplary/banner-windows-ua.json | 3 +- .../banner-without-site-publisher.json | 3 +- .../exemplary/banner-without-w-h.json | 3 +- .../silverpushtest/exemplary/banner.json | 3 +- .../exemplary/banner_without_publisher.json | 3 +- .../exemplary/video-bidfloor-zero.json | 3 +- .../silverpushtest/exemplary/video.json | 3 +- .../exemplary/video_min_max_duration.json | 3 +- .../supplemental/bad-eids-value.json | 3 +- .../supplemental/bad-imp-ext-bidder.json | 2 +- .../supplemental/bad-imp-ext.json | 2 +- .../supplemental/bad-response-unmarshal.json | 5 +- .../supplemental/status-204-resp.json | 3 +- .../supplemental/status-400-resp.json | 3 +- .../supplemental/status-500-resp.json | 3 +- adapters/smaato/banner.go | 22 + adapters/smaato/banner_test.go | 42 + adapters/smaato/image.go | 49 - adapters/smaato/image_test.go | 51 - adapters/smaato/native.go | 6 +- adapters/smaato/params_test.go | 2 +- adapters/smaato/richmedia.go | 49 - adapters/smaato/richmedia_test.go | 48 - adapters/smaato/smaato.go | 134 +- adapters/smaato/smaato_test.go | 21 +- .../exemplary/multiple-impressions-skadn.json | 24 +- .../exemplary/multiple-impressions.json | 24 +- .../exemplary/multiple-media-types-skadn.json | 24 +- .../exemplary/multiple-media-types.json | 24 +- .../smaato/smaatotest/exemplary/native.json | 66 +- .../exemplary/simple-banner-app.json | 16 +- ...Media-app.json => simple-banner-dooh.json} | 66 +- .../simple-banner-eids.json} | 47 +- .../exemplary/simple-banner-skadn.json | 16 +- .../smaatotest/exemplary/simple-banner.json | 78 +- .../smaatotest/exemplary/video-app.json | 10 +- .../smaatotest/exemplary/video-dooh.json | 225 ++ .../smaato/smaatotest/exemplary/video.json | 80 +- .../supplemental/bad-adm-response.json | 172 - .../bad-adtype-header-response.json | 9 +- .../bad-expires-header-response.json | 14 +- .../bad-imp-banner-format-request.json | 28 - .../bad-status-code-response.json | 9 +- .../supplemental/banner-w-and-h.json | 14 +- .../curl-nil-response.json} | 54 +- .../supplemental/expires-header-response.json | 14 +- ...est.json => no-app-site-dooh-request.json} | 2 +- .../supplemental/no-bid-response.json | 9 +- .../supplemental/no-consent-info-request.json | 16 +- .../outdated-expires-header-response.json | 14 +- .../video/multiple-adpods-skadn.json | 18 +- .../smaatotest/video/multiple-adpods.json | 18 +- .../smaatotest/video/single-adpod-skadn.json | 10 +- .../smaato/smaatotest/video/single-adpod.json | 10 +- ...e.json => bad-adtype-header-response.json} | 70 +- .../bad-bid-ext-response.json | 10 +- adapters/smartadserver/params_test.go | 2 +- adapters/smartadserver/smartadserver.go | 18 +- adapters/smartadserver/smartadserver_test.go | 6 +- .../exemplary/multi-banner.json | 6 +- .../exemplary/native-app.json | 4 +- .../exemplary/native-web.json | 3 +- .../exemplary/simple-banner.json | 3 +- .../exemplary/simple-video.json | 3 +- .../supplemental/request-site-recreated.json | 3 +- .../response-200-without-body.json | 7 +- .../supplemental/response-204.json | 3 +- .../supplemental/response-400.json | 3 +- .../supplemental/response-500.json | 3 +- adapters/smarthub/params_test.go | 2 +- adapters/smarthub/smarthub.go | 22 +- adapters/smarthub/smarthub_test.go | 8 +- .../smarthubtest/exemplary/banner.json | 5 +- .../smarthubtest/exemplary/native.json | 5 +- .../smarthubtest/exemplary/video.json | 5 +- .../supplemental/bad-response.json | 7 +- .../supplemental/empty-seatbid-0-bid.json | 5 +- .../supplemental/empty-seatbid.json | 5 +- .../smarthubtest/supplemental/status-204.json | 5 +- .../smarthubtest/supplemental/status-400.json | 5 +- .../smarthubtest/supplemental/status-503.json | 5 +- .../supplemental/unexpected-status.json | 5 +- .../supplemental/wrong-bidtype.json | 5 +- adapters/smartrtb/smartrtb.go | 23 +- adapters/smartrtb/smartrtb_test.go | 6 +- .../smartrtbtest/exemplary/banner.json | 3 +- .../smartrtbtest/exemplary/video.json | 3 +- .../supplemental/bad-bidder-ext.json | 4 +- .../supplemental/bad-imp-ext.json | 4 +- .../supplemental/bad-pub-value.json | 2 +- .../supplemental/bad-request.json | 3 +- .../supplemental/invalid-bid-ext.json | 3 +- .../supplemental/invalid-bid-format.json | 3 +- .../supplemental/invalid-bid-json.json | 5 +- .../supplemental/invalid-imp-ext.json | 2 +- .../smartrtbtest/supplemental/nobid.json | 3 +- .../supplemental/non-http-ok.json | 3 +- adapters/smartx/params_test.go | 2 +- adapters/smartx/smartx.go | 12 +- adapters/smartx/smartx_test.go | 6 +- .../smartx/smartxtest/exemplary/01-video.json | 3 +- .../smartxtest/exemplary/02-consent.json | 3 +- .../smartxtest/exemplary/03-device.json | 3 +- .../02-internal-server-error.json | 3 +- .../03-missing-bidder-in-response.json | 3 +- adapters/smartyads/params_test.go | 2 +- adapters/smartyads/smartyads.go | 20 +- adapters/smartyads/smartyads_test.go | 6 +- .../smartyadstest/exemplary/banner-app.json | 3 +- .../smartyadstest/exemplary/banner-web.json | 3 +- .../smartyadstest/exemplary/native-app.json | 3 +- .../smartyadstest/exemplary/native-web.json | 3 +- .../smartyadstest/exemplary/video-app.json | 3 +- .../smartyadstest/exemplary/video-web.json | 3 +- .../supplemental/empty-seatbid-array.json | 3 +- .../supplemental/invalid-response.json | 3 +- .../supplemental/status-code-bad-request.json | 3 +- .../supplemental/status-code-no-content.json | 3 +- .../supplemental/status-code-other-error.json | 3 +- .../status-code-service-unavailable.json | 3 +- adapters/smilewanted/params_test.go | 2 +- adapters/smilewanted/smilewanted.go | 16 +- adapters/smilewanted/smilewanted_test.go | 6 +- .../exemplary/simple-banner.json | 3 +- .../exemplary/simple-video.json | 3 +- .../supplemental/bad-server-response.json | 5 +- .../supplemental/status-code-204.json | 3 +- .../supplemental/status-code-400.json | 3 +- .../supplemental/unexpected-status-code.json | 3 +- adapters/smrtconnect/smrtconnect.go | 146 + adapters/smrtconnect/smrtconnect_test.go | 28 + .../smrtconnecttest/exemplary/audio-app.json | 91 + .../smrtconnecttest/exemplary/audio-web.json | 91 + .../smrtconnecttest/exemplary/banner-app.json | 140 + .../exemplary/banner-multiple-bids.json | 229 ++ .../smrtconnecttest/exemplary/banner-web.json | 128 + .../smrtconnecttest/exemplary/native-app.json | 137 + .../smrtconnecttest/exemplary/native-web.json | 125 + .../smrtconnecttest/exemplary/video-app.json | 150 + .../smrtconnecttest/exemplary/video-web.json | 148 + .../supplemental/empty-seatbid-array.json | 120 + .../invalid-aceex-ext-object.json | 29 + .../supplemental/invalid-response.json | 95 + .../supplemental/status-code-bad-request.json | 93 + .../supplemental/status-code-no-content.json | 69 + .../supplemental/status-code-other-error.json | 79 + .../status-code-service-unavailable.json | 79 + adapters/sonobi/params_test.go | 2 +- adapters/sonobi/sonobi.go | 59 +- adapters/sonobi/sonobi_test.go | 6 +- .../sonobi/sonobitest/exemplary/banner.json | 5 +- .../sonobi/sonobitest/exemplary/native.json | 143 + .../sonobi/sonobitest/exemplary/no-bid.json | 5 +- .../supplemental/currency-conversion.json | 172 + adapters/sovrn/sovrn.go | 18 +- adapters/sovrn/sovrn_test.go | 6 +- .../sovrntest/exemplary/multi-banner.json | 3 +- .../sovrn/sovrntest/exemplary/no-bid.json | 3 +- .../sovrntest/exemplary/simple-banner.json | 3 +- .../sovrntest/supplemental/adunitcode.json | 3 +- .../sovrntest/supplemental/blank-device.json | 3 +- .../both-custom-default-bidfloor.json | 3 +- .../supplemental/camel-case-tagId.json | 3 +- .../sovrn/sovrntest/supplemental/fpd.json | 3 +- .../sovrn/sovrntest/supplemental/gdpr.json | 3 +- .../supplemental/invalid-adunitcode.json | 3 +- .../supplemental/invalid-imp-id.json | 3 +- .../sovrntest/supplemental/no-bidfloor.json | 3 +- .../sovrn/sovrntest/supplemental/no-user.json | 3 +- .../supplemental/only-custom-bidfloor.json | 3 +- .../supplemental/only-default-bidfloor.json | 3 +- .../with-both-custom-default-bidfloor.json | 3 +- .../with-only-custom-bidfloor.json | 3 +- .../with-only-default-bidfloor.json | 3 +- .../sovrn/sovrntest/video/full-video.json | 3 +- .../sovrntest/video/multi-banner-video.json | 3 +- .../sovrn/sovrntest/video/multi-video.json | 3 +- adapters/sovrn/sovrntest/video/no-bid.json | 3 +- .../sovrn/sovrntest/video/simple-video.json | 3 +- adapters/sovrnXsp/params_test.go | 56 + adapters/sovrnXsp/sovrnXsp.go | 174 + adapters/sovrnXsp/sovrnXsp_test.go | 20 + .../sovrnXsptest/exemplary/banner.json | 120 + .../sovrnXsptest/exemplary/native.json | 110 + .../sovrnXsptest/exemplary/video.json | 122 + .../supplemental/request-no-matching-imp.json | 30 + .../supplemental/response-empty-seat.json | 83 + .../supplemental/response-http-error.json | 85 + .../supplemental/response-invalid-crtype.json | 104 + .../supplemental/response-nobid.json | 80 + adapters/sspBC/sspbc.go | 29 +- adapters/sspBC/sspbc_test.go | 6 +- .../exemplary/banner-fromtemplate.json | 3 +- .../banner-preformatted-multiple-imps.json | 3 +- .../banner-preformatted-onecode.json | 3 +- .../exemplary/banner-preformatted.json | 3 +- .../sspbctest/supplemental/bad_response.json | 5 +- .../bad_response_with_incorrect_impid.json | 3 +- .../bad_response_without_adm.json | 3 +- .../request_with_diffrent_siteid.json | 3 +- ...request_with_incorrect_imp_bidder_ext.json | 3 +- .../request_with_incorrect_imp_ext.json | 3 +- ...request_with_standard_and_onecode_imp.json | 3 +- .../supplemental/request_with_test.json | 3 +- .../request_without_banner_format.json | 3 +- .../supplemental/request_without_ext_id.json | 3 +- .../request_without_ext_site_id.json | 3 +- .../sspbctest/supplemental/status_204.json | 3 +- .../sspbctest/supplemental/status_400.json | 3 +- adapters/stroeerCore/params_test.go | 3 +- adapters/stroeerCore/stroeercore.go | 69 +- adapters/stroeerCore/stroeercore_test.go | 6 +- .../stroeercoretest/exemplary/dsa.json | 203 ++ .../exemplary/mobile-banner-single.json | 9 +- .../exemplary/site-banner-multi.json | 15 +- .../exemplary/site-banner-single.json | 9 +- .../exemplary/site-multi-format-single.json | 147 + .../exemplary/site-multi-types.json | 187 ++ .../exemplary/site-video-single.json | 117 + .../supplemental/bad-server-response.json | 3 +- .../supplemental/unknown-bid-media-type.json | 194 ++ adapters/suntContent/suntContent.go | 145 - adapters/suntContent/suntContent_test.go | 194 -- .../suntContenttest/exemplary/native.json | 109 - .../supplemental/invalid_tag_id.json | 30 - .../supplemental/status_bad_request.json | 72 - adapters/taboola/params_test.go | 3 +- adapters/taboola/taboola.go | 51 +- adapters/taboola/taboola_test.go | 10 +- .../taboola/taboolatest/exemplary/banner.json | 3 +- .../bannerAppRequest.json} | 18 +- .../exemplary/bannerResolveMacro.json | 3 +- .../multiFormatImpressionsRequest.json | 6 +- .../taboola/taboolatest/exemplary/native.json | 3 +- .../exemplary/nativeResolveMacro.json | 3 +- .../taboolatest/exemplary/withPageType.json | 3 +- .../taboolatest/exemplary/withPosition.json | 3 +- .../bidParamsOverrideRequestFields.json | 3 +- .../supplemental/bidderServerError.json | 3 +- .../supplemental/emptyReponseFromBidder.json | 3 +- .../incorrectResponseImpMapping.json | 3 +- .../supplemental/multiImpressionsRequest.json | 3 +- .../supplemental/noValidImpression.json | 2 +- .../supplemental/optionalParamsProvided.json | 3 +- .../supplemental/unexpectedStatusCode.json | 3 +- adapters/tappx/params_test.go | 3 +- adapters/tappx/tappx.go | 30 +- adapters/tappx/tappx_test.go | 11 +- .../single-banner-impression-extra.json | 3 +- ...ngle-banner-impression-future-feature.json | 3 +- .../exemplary/single-banner-impression.json | 3 +- .../exemplary/single-banner-site.json | 3 +- .../exemplary/single-video-impression.json | 3 +- .../exemplary/single-video-site.json | 3 +- .../tappxtest/supplemental/204status.json | 3 +- .../tappxtest/supplemental/bidfloor.json | 3 +- .../supplemental/http-err-status.json | 3 +- .../supplemental/http-err-status2.json | 3 +- adapters/teads/teads.go | 20 +- adapters/teads/teads_test.go | 13 +- .../exemplary/simple-banner-with-format.json | 5 +- .../teadstest/exemplary/simple-banner.json | 5 +- .../teadstest/exemplary/simple-video.json | 5 +- .../supplemental/bid-id-does-not-match.json | 5 +- .../supplemental/currency-empty-string.json | 5 +- .../supplemental/no-impression-response.json | 5 +- .../supplemental/renderer-name-empty.json | 5 +- .../supplemental/renderer-version-empty.json | 5 +- .../teadstest/supplemental/status-400.json | 5 +- .../teadstest/supplemental/status-500.json | 5 +- adapters/telaria/params_test.go | 3 +- adapters/telaria/telaria.go | 23 +- adapters/telaria/telaria_test.go | 6 +- .../exemplary/multiple-video-web.json | 3 +- .../exemplary/multiple-vidoe-app.json | 3 +- .../telariatest/exemplary/video-app.json | 3 +- .../telariatest/exemplary/video-web.json | 3 +- .../supplemental/invalid-response.json | 3 +- .../supplemental/status-code-bad-request.json | 3 +- .../supplemental/status-code-no-content.json | 3 +- .../supplemental/status-code-other-error.json | 3 +- .../status-code-service-unavailable.json | 3 +- adapters/theadx/params_test.go | 66 + adapters/theadx/theadx.go | 150 + adapters/theadx/theadx_test.go | 20 + .../theadxtest/exemplary/dynamic-tag.json | 101 + .../theadxtest/exemplary/multi-format.json | 163 + .../theadxtest/exemplary/multi-native.json | 129 + .../theadxtest/exemplary/single-banner.json | 106 + .../theadxtest/exemplary/single-native.json | 101 + .../theadxtest/exemplary/single-video.json | 104 + .../theadxtest/supplemental/bad-request.json | 49 + .../supplemental/empty-response.json | 43 + .../supplemental/nobid-response.json | 50 + .../theadxtest/supplemental/server-error.json | 50 + .../supplemental/unparsable-response.json | 50 + adapters/thetradedesk/params_test.go | 53 + adapters/thetradedesk/thetradedesk.go | 197 ++ adapters/thetradedesk/thetradedesk_test.go | 385 +++ .../exemplary/simple-banner-inapp.json | 144 + ...mple-banner-multiple-bids-and-formats.json | 231 ++ .../simple-banner-multiple-bids.json | 215 ++ .../exemplary/simple-banner.json | 142 + .../exemplary/simple-empty-publisherId.json | 142 + .../exemplary/simple-multi-type-banner.json | 151 + .../exemplary/simple-multi-type-video.json | 151 + .../exemplary/simple-native.json | 115 + .../exemplary/simple-video.json | 154 + .../200-response-from-target.json | 105 + .../204-response-from-target.json | 105 + .../400-response-from-target.json | 105 + .../500-response-from-target.json | 105 + .../supplemental/invalid-mtype.json | 86 + .../supplemental/invalid-publisher.json | 45 + adapters/tpmn/params_test.go | 3 +- adapters/tpmn/tpmn.go | 12 +- adapters/tpmn/tpmn_test.go | 6 +- .../tpmntest/exemplary/simple-banner.json | 3 +- .../tpmntest/exemplary/simple-native.json | 3 +- .../exemplary/simple-site-banner.json | 3 +- .../exemplary/simple-site-native.json | 3 +- .../tpmntest/exemplary/simple-site-video.json | 3 +- .../tpmn/tpmntest/exemplary/simple-video.json | 3 +- .../tpmntest/supplemental/bad-imp-ext.json | 3 +- .../tpmntest/supplemental/bad_response.json | 5 +- .../tpmntest/supplemental/no-imp-ext.json | 5 +- .../tpmntest/supplemental/status-204.json | 3 +- .../tpmntest/supplemental/status-404.json | 3 +- adapters/trafficgate/params_test.go | 2 +- adapters/trafficgate/trafficgate.go | 32 +- adapters/trafficgate/trafficgate_test.go | 6 +- .../exemplary/multiple-imps.json | 3 +- .../exemplary/simple-audio.json | 3 +- .../exemplary/simple-banner.json | 3 +- .../exemplary/simple-native.json | 3 +- .../exemplary/simple-video.json | 3 +- .../exemplary/simple-web-banner.json | 3 +- .../supplemental/bad_bidtype_response.json | 3 +- .../supplemental/bad_ext_response.json | 3 +- .../supplemental/bad_response.json | 5 +- .../supplemental/bad_status_code.json | 3 +- .../supplemental/status-204.json | 3 +- .../supplemental/status-404.json | 3 +- adapters/triplelift/triplelift.go | 22 +- adapters/triplelift/triplelift_test.go | 6 +- .../exemplary/optional-params.json | 3 +- .../exemplary/simple-banner.json | 3 +- .../exemplary/simple-video.json | 3 +- .../triplelifttest/supplemental/badext.json | 2 +- .../supplemental/badextbidder.json | 2 +- .../supplemental/badresponseext.json | 5 +- .../supplemental/badstatuscode.json | 3 +- .../supplemental/notgoodstatuscode.json | 3 +- .../supplemental/video-format-11.json | 3 +- .../supplemental/video-format-12.json | 3 +- .../supplemental/video-format-17.json | 3 +- adapters/triplelift_native/params_test.go | 53 + .../triplelift_native/triplelift_native.go | 61 +- .../triplelift_native_test.go | 6 +- .../exemplary/app-msn-no-tag-code.json | 112 + .../exemplary/app-msn.json | 112 + .../triplelift_nativetest/exemplary/app.json | 112 + .../exemplary/optional-params.json | 56 - .../exemplary/site-msn-no-tag-code.json | 112 + .../exemplary/site-msn.json | 112 + .../triplelift_nativetest/exemplary/site.json | 112 + .../supplemental/app-no-publisher.json | 25 + .../supplemental/app-publisher-no-domain.json | 60 + .../supplemental/badext.json | 2 +- .../supplemental/badextbidder.json | 2 +- .../supplemental/badresponseext.json | 3 +- .../supplemental/badstatuscode.json | 3 +- .../effective-publisher-allowed.json | 122 + .../effective-publisher-not-allowed.json | 36 + .../supplemental/no-imp-ext-data.json | 85 + .../supplemental/notgoodstatuscode.json | 86 - .../supplemental/site-no-publisher.json | 25 + .../site-publisher-no-domain.json | 62 + adapters/trustedstack/params_test.go | 54 + adapters/trustedstack/trustedstack.go | 109 + adapters/trustedstack/trustedstack_test.go | 31 + .../exemplary/multi-format.json | 84 + .../exemplary/multi-imps.json | 135 + .../trustedstacktest/exemplary/no-bid.json} | 37 +- .../exemplary/optional-params.json} | 43 +- .../exemplary/simple-banner.json | 101 + .../exemplary/simple-native.json | 91 + .../exemplary/simple-video.json | 104 + ...valid-req-400-status-code-bad-request.json | 97 + ...valid-req-500-status-code-bad-request.json | 98 + ...alid-req-200-incorrect-response-mtype.json | 125 + ...eq-200-bid-response-from-trustedstack.json | 133 + ...lid-req-200-incorrect-response-format.json | 121 + ...id-req-204-response-from-trustedstack.json | 67 + adapters/ucfunnel/params_test.go | 2 +- adapters/ucfunnel/ucfunnel.go | 20 +- adapters/ucfunnel/ucfunnel_test.go | 53 +- adapters/undertone/params_test.go | 3 +- adapters/undertone/undertone.go | 16 +- adapters/undertone/undertone_test.go | 7 +- .../exemplary/multi-imp-app-request.json | 3 +- .../exemplary/multi-imp-site-request.json | 3 +- .../supplemental/badrequest.json | 3 +- .../supplemental/internalerror.json | 3 +- .../undertonetest/supplemental/nocontent.json | 3 +- adapters/unicorn/params_test.go | 2 +- adapters/unicorn/unicorn.go | 20 +- adapters/unicorn/unicorn_test.go | 6 +- .../banner-app-no-app-publisher.json | 3 +- .../exemplary/banner-app-no-mediaid.json | 3 +- .../exemplary/banner-app-no-publisherid.json | 3 +- .../exemplary/banner-app-no-source.json | 3 +- .../exemplary/banner-app-with-ip.json | 3 +- .../exemplary/banner-app-with-ipv6.json | 3 +- .../exemplary/banner-app-without-ext.json | 3 +- .../banner-app-without-placementid.json | 3 +- .../unicorntest/exemplary/banner-app.json | 3 +- .../exemplary/banner-app_with_fpd.json | 3 +- .../exemplary/banner-app_with_no_fpd.json | 3 +- .../unicorn/unicorntest/supplemental/204.json | 3 +- .../unicorn/unicorntest/supplemental/400.json | 3 +- .../unicorn/unicorntest/supplemental/500.json | 3 +- .../supplemental/cannot-parse-accountid.json | 4 +- .../unicorntest/supplemental/no-imp-ext.json | 4 +- adapters/unruly/params_test.go | 2 +- adapters/unruly/unruly.go | 30 +- adapters/unruly/unruly_test.go | 6 +- .../exemplary/banner-and-video-app.json | 3 +- .../exemplary/banner-and-video-gdpr.json | 3 +- .../exemplary/banner-and-video-site.json | 3 +- .../exemplary/banner-and-video.json | 3 +- .../unrulytest/exemplary/simple-banner.json | 3 +- .../unrulytest/exemplary/simple-video.json | 13 +- .../supplemental/no-matching-impid.json | 3 +- .../supplemental/status-code-204.json | 3 +- .../supplemental/status-code-400.json | 3 +- .../supplemental/status-code-401.json | 3 +- adapters/vidazoo/params_test.go | 42 + adapters/vidazoo/vidazoo.go | 134 + adapters/vidazoo/vidazoo_test.go | 24 + .../vidazoo/vidazootest/exemplary/banner.json | 127 + .../vidazootest/exemplary/multi-imp.json | 265 ++ .../vidazoo/vidazootest/exemplary/video.json | 154 + .../vidazootest/supplemental/bad-request.json | 65 + .../supplemental/internal-error.json | 65 + .../vidazootest/supplemental/no-content.json | 60 + .../supplemental/unknown-bid-type.json | 109 + adapters/videobyte/params_test.go | 2 +- adapters/videobyte/videobyte.go | 18 +- adapters/videobyte/videobyte_test.go | 4 +- .../videobytetest/exemplary/banner.json | 3 +- .../exemplary/empty-placement-network.json | 3 +- .../exemplary/empty-site-domain-ref.json | 3 +- .../videobytetest/exemplary/multi-format.json | 3 +- .../videobytetest/exemplary/multi-imp.json | 6 +- .../videobytetest/exemplary/video.json | 3 +- .../supplemental/invalid-imp-ext-bidder.json | 2 +- .../supplemental/invalid-imp-ext.json | 2 +- .../supplemental/invalid-response.json | 3 +- .../supplemental/status-code-bad-request.json | 3 +- .../supplemental/status-code-no-content.json | 3 +- .../supplemental/status-code-other-error.json | 3 +- adapters/videoheroes/params_test.go | 2 +- adapters/videoheroes/videoheroes.go | 20 +- adapters/videoheroes/videoheroes_test.go | 6 +- .../videoheroestest/exemplary/banner-app.json | 3 +- .../videoheroestest/exemplary/banner-web.json | 3 +- .../videoheroestest/exemplary/native-app.json | 3 +- .../videoheroestest/exemplary/native-web.json | 3 +- .../videoheroestest/exemplary/video-app.json | 3 +- .../videoheroestest/exemplary/video-web.json | 3 +- .../supplemental/empty-seatbid-array.json | 3 +- .../supplemental/invalid-response.json | 3 +- .../supplemental/status-code-bad-request.json | 3 +- .../supplemental/status-code-no-content.json | 3 +- .../supplemental/status-code-other-error.json | 3 +- .../status-code-service-unavailable.json | 3 +- adapters/vidoomy/params_test.go | 2 +- adapters/vidoomy/vidoomy.go | 21 +- adapters/vidoomy/vidoomy_test.go | 6 +- .../multi-impression-video-banner.json | 6 +- .../exemplary/simple-app-banner.json | 3 +- .../vidoomytest/exemplary/simple-banner.json | 3 +- .../exemplary/simple-site-video.json | 3 +- .../supplemental/server-error.json | 3 +- .../server-response-wrong-impid.json | 3 +- .../simple-banner-no-response.json | 3 +- adapters/visiblemeasures/params_test.go | 2 +- adapters/visiblemeasures/visiblemeasures.go | 18 +- .../visiblemeasures/visiblemeasures_test.go | 6 +- .../exemplary/endpointId.json | 3 +- .../exemplary/simple-banner.json | 3 +- .../exemplary/simple-native.json | 3 +- .../exemplary/simple-video.json | 3 +- .../exemplary/simple-web-banner.json | 3 +- .../supplemental/bad_media_type.json | 3 +- .../supplemental/bad_response.json | 5 +- .../supplemental/status-204.json | 3 +- .../supplemental/status-not-200.json | 3 +- adapters/visx/params_test.go | 2 +- adapters/visx/visx.go | 16 +- adapters/visx/visx_test.go | 6 +- .../visx/visxtest/exemplary/headers_ipv4.json | 3 +- .../visx/visxtest/exemplary/headers_ipv6.json | 3 +- .../exemplary/multitype-banner-response.json | 3 +- .../exemplary/multitype-video-response.json | 3 +- .../visxtest/exemplary/simple-banner.json | 3 +- .../visx/visxtest/exemplary/simple-video.json | 3 +- .../visxtest/exemplary/with_currency.json | 3 +- .../visxtest/supplemental/bad_response.json | 5 +- .../visxtest/supplemental/status_204.json | 3 +- .../visxtest/supplemental/status_400.json | 3 +- .../visxtest/supplemental/status_418.json | 3 +- .../visxtest/supplemental/wrong_imp_type.json | 3 +- .../visxtest/supplemental/wrong_impid.json | 3 +- adapters/vox/params_test.go | 2 +- adapters/vox/vox.go | 13 +- adapters/vox/vox_test.go | 6 +- adapters/vox/voxtest/exemplary/banner.json | 3 +- adapters/vox/voxtest/exemplary/video.json | 3 +- .../response_204_to_nocontent.json | 3 +- .../supplemental/response_500_to_error.json | 3 +- adapters/vrtcal/params_test.go | 2 +- adapters/vrtcal/vrtcal.go | 14 +- adapters/vrtcal/vrtcal_test.go | 6 +- .../vrtcaltest/exemplary/simple-banner.json | 3 +- .../vrtcaltest/exemplary/simple-native.json | 3 +- .../vrtcaltest/exemplary/simple-video.json | 3 +- .../exemplary/web-simple-banner.json | 3 +- .../exemplary/web-simple-native.json | 3 +- .../exemplary/web-simple-video.json | 3 +- .../supplemental/unsupported_return_type.json | 3 +- adapters/{liftoff => vungle}/param_test.go | 8 +- .../{liftoff/liftoff.go => vungle/vungle.go} | 51 +- adapters/vungle/vungle_test.go | 21 + .../exemplary/app_video_instl.json | 8 +- .../exemplary/app_video_rewarded.json | 8 +- .../exemplary/site_video_instl.json | 153 + .../exemplary/site_video_rewarded.json | 146 + .../supplemental/appid_placementid_check.json | 8 +- .../missing_appid_or_placementid.json | 5 +- .../no_site_or_app_video_rewarded.json | 48 + .../supplemental/response_code_204.json | 5 +- .../supplemental/response_code_400.json | 5 +- .../supplemental/response_code_non_200.json | 5 +- .../supplemental/vungle_ext_check.json} | 8 +- adapters/xeworks/params_test.go | 2 +- adapters/xeworks/xeworks.go | 22 +- adapters/xeworks/xeworks_test.go | 6 +- .../xeworks/xeworkstest/exemplary/banner.json | 6 +- .../xeworks/xeworkstest/exemplary/native.json | 3 +- .../xeworks/xeworkstest/exemplary/video.json | 3 +- .../supplemental/bad-response.json | 5 +- .../supplemental/empty-mediatype.json | 3 +- .../supplemental/empty-seatbid-0-bid.json | 3 +- .../supplemental/empty-seatbid.json | 3 +- .../invalid-ext-bidder-object.json | 2 +- .../supplemental/invalid-ext-object.json | 2 +- .../supplemental/invalid-mediatype.json | 3 +- .../xeworkstest/supplemental/status-204.json | 3 +- .../xeworkstest/supplemental/status-400.json | 3 +- .../xeworkstest/supplemental/status-503.json | 3 +- .../supplemental/unexpected-status.json | 3 +- adapters/yahooAds/params_test.go | 2 +- adapters/yahooAds/yahooAds.go | 29 +- adapters/yahooAds/yahooAds_test.go | 6 +- .../exemplary/simple-app-banner.json | 3 +- .../yahooAdstest/exemplary/simple-banner.json | 3 +- .../yahooAdstest/exemplary/simple-video.json | 3 +- .../non-supported-requests-bids-ignored.json | 3 +- .../supplemental/server-error.json | 3 +- .../server-response-wrong-impid.json | 3 +- .../simple-banner-gpp-overwrite.json | 3 +- .../supplemental/simple-banner-gpp.json | 3 +- ...nner-ignore-width-when-height-missing.json | 3 +- adapters/yandex/params_test.go | 87 + adapters/yandex/yandex.go | 341 ++ adapters/yandex/yandex_test.go | 29 + .../yandex/yandextest/exemplary/native.json | 135 + .../yandextest/exemplary/simple-banner.json | 135 + .../multiple-imps-some-malformed.json | 146 + .../supplemental/multiple-imps.json | 237 ++ .../simple-banner-empty-response.json | 101 + .../simple-banner-empty-seatbid.json | 106 + .../supplemental/simple-banner-sizes.json | 233 ++ .../simple-banner-status-400.json | 102 + .../simple-banner-unknown-imp.json | 125 + .../simple-banner-unparsable-body.json | 102 + .../supplemental/unknown-banner.json | 32 + adapters/yeahmobi/params_test.go | 3 +- adapters/yeahmobi/yeahmobi.go | 56 +- adapters/yeahmobi/yeahmobi_test.go | 6 +- .../yeahmobitest/exemplary/no-bid.json | 3 +- .../yeahmobitest/exemplary/simple-banner.json | 3 +- .../exemplary/simple-native-1.1.json | 3 +- .../yeahmobitest/exemplary/simple-native.json | 3 +- .../yeahmobitest/exemplary/simple-video.json | 43 +- .../supplemental/bad_imp_ext.json | 2 +- .../supplemental/bad_imp_ext_bidder.json | 2 +- .../supplemental/bad_response.json | 5 +- .../yeahmobitest/supplemental/status_400.json | 3 +- .../yeahmobitest/supplemental/status_500.json | 3 +- adapters/yieldlab/params_test.go | 2 +- adapters/yieldlab/types.go | 57 +- adapters/yieldlab/yieldlab.go | 152 +- adapters/yieldlab/yieldlab_test.go | 100 +- .../yieldlabtest/exemplary/banner.json | 3 +- .../yieldlab/yieldlabtest/exemplary/dsa.json | 170 + .../yieldlab/yieldlabtest/exemplary/gdpr.json | 3 +- .../yieldlabtest/exemplary/mixed_types.json | 3 +- .../exemplary/multiple_impressions.json | 3 +- .../yieldlabtest/exemplary/schain.json | 3 +- .../exemplary/schain_multiple_nodes.json | 3 +- .../yieldlabtest/exemplary/video.json | 3 +- .../yieldlabtest/exemplary/video_app.json | 3 +- .../yieldlabtest/supplemental/dsa_empty.json | 128 + .../supplemental/dsa_empty_transparency.json | 140 + .../supplemental/invalid_reg_ext.json | 67 + adapters/yieldmo/params_test.go | 2 +- adapters/yieldmo/yieldmo.go | 107 +- adapters/yieldmo/yieldmo_test.go | 6 +- .../yieldmotest/exemplary/app-banner.json | 15 +- .../yieldmotest/exemplary/app_video.json | 15 +- .../yieldmotest/exemplary/simple-banner.json | 33 +- .../yieldmotest/exemplary/simple_video.json | 15 +- .../exemplary/valid_currency_conversion.json | 150 + .../yieldmotest/exemplary/with_gpid.json | 15 +- .../supplemental/unsupported_currency.json | 109 + adapters/yieldone/params_test.go | 3 +- adapters/yieldone/yieldone.go | 18 +- adapters/yieldone/yieldone_test.go | 6 +- .../yieldonetest/exemplary/simple-banner.json | 3 +- .../yieldonetest/exemplary/simple-video.json | 3 +- .../supplemental/bad_response.json | 5 +- .../yieldonetest/supplemental/status_204.json | 3 +- .../yieldonetest/supplemental/status_400.json | 3 +- .../yieldonetest/supplemental/status_418.json | 3 +- adapters/zeroclickfraud/zeroclickfraud.go | 23 +- .../zeroclickfraud/zeroclickfraud_test.go | 6 +- .../exemplary/multi-request.json | 3 +- .../zeroclickfraudtest/exemplary/native.json | 3 +- .../exemplary/simple-banner.json | 3 +- .../exemplary/simple-video.json | 3 +- .../supplemental/bad-response-body.json | 5 +- .../supplemental/bad-server-response.json | 3 +- .../supplemental/missing-ext.json | 4 +- .../supplemental/missing-extparam.json | 4 +- .../supplemental/no-content-response.json | 3 +- .../exemplary/banner.json | 5 +- .../exemplary/no-bid.json | 5 +- .../zeta_global_ssp-test/exemplary/video.json | 5 +- .../supplemental/bad-request.json | 5 +- .../supplemental/invalid-bid-type.json | 5 +- .../supplemental/no-bid-type.json.json | 5 +- .../supplemental/server-error.json | 5 +- adapters/zeta_global_ssp/zeta_global_ssp.go | 16 +- .../zeta_global_ssp/zeta_global_ssp_test.go | 8 +- adapters/zmaticoo/params_test.go | 48 + adapters/zmaticoo/zmaticoo.go | 152 + adapters/zmaticoo/zmaticoo_test.go | 20 + .../zmaticootest/exemplary/no-bid.json | 59 + .../zmaticootest/exemplary/simple-banner.json | 94 + .../exemplary/simple-native-1.1.json | 84 + .../zmaticootest/exemplary/simple-native.json | 84 + .../zmaticootest/exemplary/simple-video.json | 92 + .../supplemental/bad_imp_ext.json | 21 + .../supplemental/bad_imp_ext_bidder.json | 27 + .../supplemental/bad_response.json | 64 + .../supplemental/empty_imp_ext_bidder.json} | 25 +- .../zmaticootest/supplemental/status_400.json | 64 + .../zmaticootest/supplemental/status_500.json | 64 + adservertargeting/adservertargeting.go | 4 +- adservertargeting/adservertargeting_test.go | 8 +- adservertargeting/reqcache.go | 4 +- adservertargeting/requestcache_test.go | 2 +- adservertargeting/requestlookup.go | 7 +- adservertargeting/requestlookup_test.go | 4 +- adservertargeting/respdataprocessor.go | 10 +- adservertargeting/respdataprocessor_test.go | 6 +- adservertargeting/utils.go | 7 +- amp/parse.go | 16 +- amp/parse_test.go | 208 +- analytics/agma/README.md | 28 + analytics/agma/agma_module.go | 271 ++ analytics/agma/agma_module_test.go | 735 ++++ analytics/agma/model.go | 50 + analytics/agma/model_test.go | 46 + analytics/agma/sender.go | 84 + analytics/agma/sender_test.go | 133 + analytics/build/build.go | 143 +- analytics/build/build_test.go | 445 ++- analytics/core.go | 9 +- analytics/filesystem/file_module.go | 33 +- analytics/filesystem/file_module_test.go | 31 +- analytics/filesystem/model.go | 10 +- analytics/pubstack/configupdate.go | 2 +- analytics/pubstack/eventchannel/sender.go | 4 +- analytics/pubstack/helpers/json.go | 6 +- analytics/pubstack/helpers/json_test.go | 4 +- analytics/pubstack/helpers/model.go | 10 +- analytics/pubstack/pubstack_module.go | 12 +- analytics/pubstack/pubstack_module_test.go | 6 +- analytics/runner.go | 3 +- bidadjustment/apply.go | 6 +- bidadjustment/apply_test.go | 6 +- bidadjustment/build_rules.go | 12 +- bidadjustment/build_rules_test.go | 260 +- bidadjustment/validate.go | 2 +- bidadjustment/validate_test.go | 2 +- config/account.go | 86 +- config/account_test.go | 172 +- config/bidderinfo.go | 159 +- config/bidderinfo_test.go | 293 +- config/compression.go | 2 +- config/compression_test.go | 2 +- config/config.go | 223 +- config/config_test.go | 512 ++- config/events.go | 8 +- currency/currency.go | 42 + currency/currency_mock.go | 20 + currency/currency_test.go | 199 ++ currency/rate_converter.go | 9 +- currency/rate_converter_test.go | 2 +- currency/rates_test.go | 2 +- currency/validation.go | 4 +- currency/validation_test.go | 4 +- docs/build/README.md | 110 + docs/developers/configuration.md | 160 +- docs/developers/features.md | 12 - docs/endpoints.md | 1 - dsa/validate.go | 103 + dsa/validate_test.go | 396 +++ dsa/writer.go | 33 + dsa/writer_test.go | 189 ++ endpoints/cookie_sync.go | 207 +- endpoints/cookie_sync_test.go | 656 +++- endpoints/currency_rates.go | 4 +- endpoints/currency_rates_test.go | 2 +- endpoints/events/account_test.go | 8 +- endpoints/events/event.go | 29 +- endpoints/events/event_test.go | 14 +- endpoints/events/vtrack.go | 51 +- endpoints/events/vtrack_test.go | 12 +- endpoints/getuids.go | 4 +- endpoints/getuids_test.go | 2 +- endpoints/info/bidders.go | 68 +- endpoints/info/bidders_detail.go | 52 +- endpoints/info/bidders_detail_test.go | 226 +- endpoints/info/bidders_test.go | 237 +- endpoints/openrtb2/amp_auction.go | 109 +- endpoints/openrtb2/amp_auction_test.go | 448 ++- endpoints/openrtb2/auction.go | 920 ++--- endpoints/openrtb2/auction_benchmark_test.go | 32 +- endpoints/openrtb2/auction_test.go | 2013 ++++++----- endpoints/openrtb2/interstitial.go | 8 +- endpoints/openrtb2/interstitial_test.go | 4 +- ...simple.json => default-request-alias.json} | 12 +- .../sample-requests/aliased/hard-alias.json | 62 + .../aliased/request-alias.json | 61 + .../alternate-bidder-code.json | 67 + .../addtl-consent-through-query.json | 3 +- .../gdpr-ccpa-through-query.json | 7 +- ...dpr-legacy-tcf2-consent-through-query.json | 11 +- .../gdpr-tcf1-consent-through-query.json | 7 +- .../gdpr-tcf2-consent-through-query.json | 11 +- .../ortb-2.5-to-2.6-upconvert.json | 305 ++ .../blocked-app.json} | 4 +- .../disabled/good/partial.json | 2 +- .../hooks/auction_bidder_reject.json | 2 +- .../hooks/auction_bidder_response_reject.json | 2 +- .../hooks/auction_entrypoint_reject.json | 2 +- ...tion_processed_auction_request_reject.json | 2 +- .../auction_raw_auction_request_reject.json | 2 +- ....json => imp-ext-prebid-bidder-empty.json} | 5 +- .../invalid-whole/imp-ext-unknown-bidder.json | 2 +- .../invalid-whole/invalid-bidder-params.json | 38 + .../invalid-whole/regs-ext-gdpr-string.json | 48 - .../invalid-whole/regs-ext-malformed.json | 2 +- ...pr-invalid.json => regs-gdpr-invalid.json} | 6 +- ...empty.json => user-eids-source-empty.json} | 13 +- ...mpty.json => user-eids-uids-id-empty.json} | 15 +- ...ssing.json => user-eids-uids-missing.json} | 12 +- .../invalid-whole/user-ext-consent-int.json | 2 +- .../user-ext-eids-source-duplicate.json | 49 - .../user-gdpr-consent-invalid.json | 2 +- .../exemplary/all-ext-case-insensitive.json | 6 +- .../valid-whole/exemplary/all-ext.json | 2 + .../valid-whole/exemplary/device-sua.json | 100 + .../exemplary/ortb-2.5-to-2.6-upconvert.json | 393 +++ .../ortb-2.6-to-2.5-downconvert.json | 417 +++ .../valid-whole/exemplary/simple.json | 12 +- .../valid-whole/exemplary/source-schain.json | 91 + .../targeting-optional-all-false.json | 69 + ...targeting-optional-includeformat-only.json | 89 + .../targeting-optional-includeformat.json | 69 + .../exemplary/user-ext-eids-empty.json | 101 +- .../user-ext-eids-source-duplicate.json | 76 + .../exemplary/user-ext-prebid-buyeruids.json | 170 + .../supplementary/gdpr-conflict.json | 5 +- .../supplementary/gdpr-conflict2.json | 5 +- ...ed-bid-resp-case-matching-bidder-name.json | 39 + ...d-resp-case-not-matching-bidder-name.json} | 2 +- ...red-bid-resp-non-existing-bidder-name.json | 34 + .../imp-with-stored-bid-resp.json | 6 +- .../req-ext-bidder-params-merge.json | 1 - ...n => req-ext-bidder-params-promotion.json} | 8 +- .../supplementary/req-ext-bidder-params.json | 1 - .../req-two-imps-stored-bid-responses.json | 10 +- ...with-and-without-stored-bid-responses.json | 4 +- .../supplementary/us-privacy-invalid.json | 4 +- .../video_invalid_sample_negative_tmax.json | 87 + endpoints/openrtb2/test_utils.go | 191 +- endpoints/openrtb2/video_auction.go | 84 +- endpoints/openrtb2/video_auction_test.go | 185 +- endpoints/setuid.go | 49 +- endpoints/setuid_test.go | 112 +- endpoints/version.go | 2 +- errortypes/code.go | 6 +- errortypes/errortypes.go | 56 +- errortypes/scope.go | 19 + errortypes/scope_test.go | 37 + exchange/adapter_builders.go | 462 +-- exchange/adapter_util.go | 10 +- exchange/adapter_util_test.go | 14 +- exchange/auction.go | 38 +- exchange/auction_response.go | 4 +- exchange/auction_test.go | 80 +- exchange/bidder.go | 139 +- exchange/bidder_test.go | 339 +- exchange/bidder_validate_bids.go | 36 +- exchange/bidder_validate_bids_test.go | 34 +- exchange/entities/entities.go | 5 +- exchange/events.go | 12 +- exchange/events_test.go | 6 +- exchange/exchange.go | 232 +- exchange/exchange_test.go | 1618 +++++---- .../exchangetest/alternate-bidder-codes.json | 260 ++ .../exchangetest/append-bidder-names.json | 12 +- .../exchangetest/bid-consolidation-test.json | 6 + .../bid-ext-prebid-collision.json | 2 + exchange/exchangetest/bid-ext.json | 2 + exchange/exchangetest/bid-id-invalid.json | 197 -- exchange/exchangetest/bid-id-valid.json | 190 -- ...e_validation_enforce_one_bid_rejected.json | 26 + ...id_response_validation_enforce_secure.json | 44 + ...bid_response_validation_warn_creative.json | 4 + .../bid_response_validation_warn_secure.json | 4 + .../exchangetest/ccpa-featureflag-on.json | 11 +- exchange/exchangetest/debuglog_disabled.json | 12 +- exchange/exchangetest/debuglog_enabled.json | 12 +- .../exchangetest/dsa-default-ignored.json | 146 + exchange/exchangetest/dsa-default.json | 205 ++ exchange/exchangetest/dsa-not-required.json | 133 + exchange/exchangetest/dsa-required.json | 218 ++ .../eidpermissions-allowed-alias.json | 2 + ...dpermissions-allowed-case-insensitive.json | 2 + .../exchangetest/eidpermissions-allowed.json | 2 + .../exchangetest/eidpermissions-denied.json | 24 +- .../events-bid-account-off-request-off.json | 4 + .../events-bid-account-off-request-on.json | 4 + .../events-bid-account-on-request-off.json | 4 + .../events-vast-account-off-request-off.json | 6 + .../events-vast-account-off-request-on.json | 16 +- .../events-vast-account-on-request-off.json | 6 + .../extra-bids-with-aliases-adaptercode.json | 8 - exchange/exchangetest/extra-bids.json | 12 - ...rtydata-amp-imp-ext-one-prebid-bidder.json | 2 + ...ydata-imp-ext-multiple-prebid-bidders.json | 4 + ...stpartydata-imp-ext-one-prebid-bidder.json | 2 + .../firstpartydata-multibidder-user-eids.json | 239 ++ ...firstpartydata-user-eids-req-user-nil.json | 203 ++ ...stpartydata-user-nileids-req-user-nil.json | 163 + exchange/exchangetest/fledge-with-bids.json | 2 + .../exchangetest/floors_deal_enforcement.json | 147 + exchange/exchangetest/floors_enforcement.json | 33 +- .../exchangetest/generate-bid-id-error.json | 203 ++ .../exchangetest/generate-bid-id-many.json | 170 + .../exchangetest/generate-bid-id-one.json | 129 + exchange/exchangetest/imp-fpd-invalid.json | 55 + .../imp-fpd-multiple-bidders.json | 202 ++ .../exchangetest/imp-fpd-multiple-imp.json | 290 ++ .../imp-fpd-skipped-validation.json | 137 + .../exchangetest/include-brand-category.json | 12 +- ...epricegranularity-banner-video-native.json | 6 + .../mediatypepricegranularity-native.json | 4 + .../multi-bid-default-bid-limit.json | 10 - .../multi-bids-different-ortb-versions.json | 276 ++ exchange/exchangetest/multi-bids-error.json | 12 - .../exchangetest/multi-bids-mixed-case.json | 12 - .../multi-bids-with-extra-bids.json | 12 - exchange/exchangetest/multi-bids.json | 12 - .../exchangetest/passthrough_imp_only.json | 4 + .../passthrough_root_and_imp.json | 2 + .../exchangetest/passthrough_root_only.json | 2 + .../request-ext-prebid-filtering.json | 2 + .../request-imp-ext-prebid-filtering.json | 2 + .../request-multi-bidders-debug-info.json | 2 + .../request-multi-bidders-one-no-resp.json | 2 + .../exchangetest/schain-host-and-request.json | 39 +- exchange/exchangetest/schain-host-only.json | 27 +- .../targeting-always-include-deals.json | 291 ++ .../targeting-cache-vast-banner.json | 2 + .../exchangetest/targeting-cache-vast.json | 2 + .../exchangetest/targeting-cache-zero.json | 2 + exchange/exchangetest/targeting-mobile.json | 8 + .../exchangetest/targeting-no-winners.json | 8 + .../exchangetest/targeting-only-winners.json | 8 + .../exchangetest/targeting-with-winners.json | 8 + exchange/gdpr.go | 27 +- exchange/gdpr_test.go | 66 +- exchange/non_bid_reason.go | 55 +- exchange/non_bid_reason_test.go | 65 + exchange/price_granularity.go | 5 +- exchange/price_granularity_test.go | 6 +- exchange/seat_non_bids.go | 60 +- exchange/seat_non_bids_test.go | 535 ++- exchange/targeting.go | 37 +- exchange/targeting_test.go | 109 +- exchange/tmax_adjustments.go | 2 +- exchange/tmax_adjustments_test.go | 2 +- exchange/utils.go | 895 ++--- exchange/utils_test.go | 2967 +++++++++++------ experiment/adscert/inprocesssigner.go | 5 +- experiment/adscert/remotesigner.go | 5 +- experiment/adscert/signer.go | 3 +- experiment/adscert/signer_test.go | 5 +- firstpartydata/extmerger.go | 60 - firstpartydata/extmerger_test.go | 109 - firstpartydata/first_party_data.go | 185 +- firstpartydata/first_party_data_test.go | 902 +---- floors/enforce.go | 10 +- floors/enforce_test.go | 14 +- floors/fetcher.go | 335 ++ floors/fetcher_test.go | 1307 ++++++++ floors/floors.go | 128 +- floors/floors_test.go | 1579 +++++++-- floors/rule.go | 18 +- floors/rule_test.go | 25 +- floors/validate.go | 6 +- floors/validate_test.go | 51 +- gdpr/aggregated_config.go | 10 +- gdpr/aggregated_config_test.go | 42 +- gdpr/basic_enforcement.go | 13 +- gdpr/basic_enforcement_test.go | 17 +- gdpr/full_enforcement.go | 9 +- gdpr/full_enforcement_test.go | 67 +- gdpr/gdpr.go | 8 +- gdpr/gdpr_test.go | 6 +- gdpr/impl.go | 49 +- gdpr/impl_test.go | 51 +- gdpr/purpose_config.go | 17 +- gdpr/purpose_config_test.go | 92 +- gdpr/purpose_enforcer.go | 12 +- gdpr/purpose_enforcer_test.go | 56 +- gdpr/signal.go | 2 +- gdpr/vendorlist-fetching.go | 2 +- gdpr/vendorlist-fetching_test.go | 4 +- go.mod | 42 +- go.sum | 69 +- hooks/empty_plan.go | 4 +- hooks/hookanalytics/analytics_test.go | 2 +- hooks/hookexecution/context.go | 16 +- hooks/hookexecution/enricher.go | 6 +- hooks/hookexecution/enricher_test.go | 10 +- hooks/hookexecution/errors.go | 2 +- hooks/hookexecution/execution.go | 60 +- hooks/hookexecution/execution_test.go | 235 ++ hooks/hookexecution/executor.go | 66 +- hooks/hookexecution/executor_test.go | 139 +- hooks/hookexecution/mocks_test.go | 42 +- hooks/hookexecution/outcome.go | 2 +- hooks/hookexecution/test_utils.go | 2 +- hooks/hookstage/allprocessedbidresponses.go | 4 +- hooks/hookstage/auctionresponse.go | 2 +- hooks/hookstage/bidderrequest.go | 20 +- hooks/hookstage/bidderrequest_mutations.go | 13 +- hooks/hookstage/invocation.go | 2 +- hooks/hookstage/processedauctionrequest.go | 12 +- hooks/hookstage/rawbidderresponse.go | 11 +- .../hookstage/rawbidderresponse_mutations.go | 7 +- hooks/plan.go | 4 +- hooks/plan_test.go | 6 +- hooks/repo.go | 2 +- hooks/repo_test.go | 2 +- injector/injector.go | 401 +++ injector/injector_test.go | 455 +++ macros/macros.go | 6 + macros/provider.go | 23 +- macros/provider_test.go | 16 +- macros/replacer.go | 4 +- macros/string_index_based_replacer.go | 11 +- macros/string_index_based_replacer_test.go | 47 +- main.go | 21 +- main_test.go | 2 +- metrics/config/metrics.go | 19 +- metrics/config/metrics_test.go | 31 +- metrics/go_metrics.go | 24 +- metrics/go_metrics_test.go | 68 +- metrics/metrics.go | 11 +- metrics/metrics_mock.go | 7 +- metrics/prometheus/preload.go | 10 +- metrics/prometheus/prometheus.go | 23 +- metrics/prometheus/prometheus_test.go | 59 +- modules/builder.go | 6 +- .../fiftyonedegrees/devicedetection/README.md | 255 ++ .../devicedetection/account_info_extractor.go | 37 + .../account_info_extractor_test.go | 74 + .../devicedetection/account_validator.go | 28 + .../devicedetection/account_validator_test.go | 71 + .../fiftyonedegrees/devicedetection/config.go | 80 + .../devicedetection/config_test.go | 119 + .../devicedetection/context.go | 8 + .../devicedetection/device_detector.go | 157 + .../devicedetection/device_detector_test.go | 190 ++ .../devicedetection/device_info_extractor.go | 121 + .../device_info_extractor_test.go | 130 + .../devicedetection/evidence_extractor.go | 118 + .../evidence_extractor_test.go | 256 ++ .../devicedetection/fiftyone_device_types.go | 77 + .../fiftyone_device_types_test.go | 90 + .../hook_auction_entrypoint.go | 27 + .../hook_raw_auction_request.go | 173 + .../fiftyonedegrees/devicedetection/models.go | 66 + .../devicedetection/models_test.go | 63 + .../fiftyonedegrees/devicedetection/module.go | 107 + .../devicedetection/module_test.go | 703 ++++ .../request_headers_extractor.go | 47 + .../request_headers_extractor_test.go | 118 + .../devicedetection/sample/pbs.json | 84 + .../devicedetection/sample/request_data.json | 114 + .../devicedetection/sua_payload_extractor.go | 144 + modules/generator/builder.tmpl | 17 +- modules/generator/buildergen.go | 21 +- modules/helpers.go | 4 +- modules/moduledeps/deps.go | 9 +- modules/modules.go | 8 +- modules/modules_test.go | 8 +- modules/prebid/ortb2blocking/analytics.go | 4 +- modules/prebid/ortb2blocking/config.go | 4 +- modules/prebid/ortb2blocking/config_test.go | 2 +- .../ortb2blocking/hook_bidderrequest.go | 30 +- .../ortb2blocking/hook_raw_bidder_response.go | 16 +- modules/prebid/ortb2blocking/module.go | 6 +- modules/prebid/ortb2blocking/module_test.go | 427 +-- modules/prebid/ortb2blocking/utils.go | 4 +- openrtb_ext/alternatebiddercodes.go | 31 +- openrtb_ext/alternatebiddercodes_test.go | 148 + openrtb_ext/bid.go | 8 + openrtb_ext/bid_request_video.go | 2 +- openrtb_ext/bidders.go | 136 +- openrtb_ext/bidders_validate_test.go | 2 +- openrtb_ext/convert_down.go | 30 +- openrtb_ext/convert_down_test.go | 90 +- openrtb_ext/convert_up.go | 4 +- openrtb_ext/convert_up_test.go | 6 +- openrtb_ext/deal_tier.go | 10 +- openrtb_ext/deal_tier_test.go | 71 +- openrtb_ext/device.go | 2 +- openrtb_ext/device_test.go | 2 +- openrtb_ext/floors.go | 66 +- openrtb_ext/floors_test.go | 262 ++ openrtb_ext/imp.go | 16 + openrtb_ext/imp_adelement.go | 5 + openrtb_ext/imp_admatic.go | 6 + openrtb_ext/imp_adtarget.go | 10 +- openrtb_ext/imp_adtelligent.go | 10 +- openrtb_ext/imp_adtonos.go | 5 + openrtb_ext/imp_aidem.go | 8 +- openrtb_ext/imp_alkimi.go | 9 + openrtb_ext/imp_appnexus.go | 2 +- openrtb_ext/imp_appnexus_test.go | 2 +- openrtb_ext/imp_aso.go | 5 + openrtb_ext/imp_bidmatic.go | 11 + openrtb_ext/imp_bigoad.go | 5 + .../{imp_bizzclick.go => imp_blasto.go} | 4 +- openrtb_ext/imp_bwx.go | 6 + openrtb_ext/imp_cointraffic.go | 5 + openrtb_ext/imp_concert.go | 9 + openrtb_ext/imp_connectad.go | 8 +- openrtb_ext/imp_consumable.go | 3 +- openrtb_ext/imp_copper6ssp.go | 6 + openrtb_ext/imp_displayio.go | 7 + openrtb_ext/imp_driftpixel.go | 6 + openrtb_ext/imp_escalax.go | 6 + openrtb_ext/imp_freewheelssp.go | 2 +- openrtb_ext/imp_gumgum.go | 9 +- openrtb_ext/imp_loyal.go | 6 + openrtb_ext/imp_mediago.go | 13 + openrtb_ext/imp_melozen.go | 5 + openrtb_ext/imp_metax.go | 7 + openrtb_ext/imp_minutemedia.go | 6 + openrtb_ext/imp_missena.go | 7 + openrtb_ext/imp_oms.go | 6 + openrtb_ext/imp_openweb.go | 7 +- openrtb_ext/imp_openx.go | 6 +- openrtb_ext/imp_oraki.go | 6 + openrtb_ext/imp_playdigo.go | 6 + openrtb_ext/imp_pubrise.go | 6 + openrtb_ext/imp_pulsepoint.go | 8 +- openrtb_ext/imp_qt.go | 6 + openrtb_ext/imp_readpeak.go | 8 + openrtb_ext/imp_relevantdigital.go | 8 + openrtb_ext/imp_rise.go | 1 + openrtb_ext/imp_roulax.go | 6 + openrtb_ext/imp_seedingAlliance.go | 4 +- openrtb_ext/imp_smrtconnect.go | 5 + openrtb_ext/imp_sovrnXsp.go | 8 + openrtb_ext/imp_suntContent.go | 5 - openrtb_ext/imp_theadx.go | 12 + openrtb_ext/imp_thetradedesk.go | 8 + openrtb_ext/imp_trustedstack.go | 6 + openrtb_ext/imp_vidazoo.go | 6 + openrtb_ext/{imp_liftoff.go => imp_vungle.go} | 2 +- openrtb_ext/imp_yandex.go | 16 + openrtb_ext/imp_zmaticoo.go | 7 + openrtb_ext/multibid_test.go | 2 +- openrtb_ext/regs.go | 49 + openrtb_ext/regs_test.go | 97 + openrtb_ext/request.go | 88 +- openrtb_ext/request_test.go | 6 +- openrtb_ext/request_wrapper.go | 184 +- openrtb_ext/request_wrapper_test.go | 456 ++- openrtb_ext/response.go | 15 +- openrtb_ext/site_test.go | 4 +- openrtb_ext/source.go | 2 +- openrtb_ext/supplyChain.go | 4 +- openrtb_ext/supplyChain_test.go | 4 +- openrtb_ext/user.go | 2 +- ortb/clone.go | 206 +- ortb/clone_test.go | 701 ++-- ortb/default.go | 34 +- ortb/default_test.go | 41 +- ortb/request_validator.go | 195 ++ ortb/request_validator_audio.go | 34 + ortb/request_validator_audio_test.go | 99 + ortb/request_validator_banner.go | 95 + ortb/request_validator_banner_test.go | 288 ++ ortb/request_validator_native.go | 273 ++ ortb/request_validator_native_test.go | 428 +++ ortb/request_validator_pmp.go | 20 + ortb/request_validator_pmp_test.go | 103 + ortb/request_validator_test.go | 299 ++ ortb/request_validator_video.go | 34 + ortb/request_validator_video_test.go | 111 + pbs/usersync.go | 6 +- prebid_cache_client/client.go | 4 +- prebid_cache_client/client_test.go | 8 +- privacy/activitycontrol.go | 11 +- privacy/activitycontrol_test.go | 32 +- privacy/ccpa/consentwriter.go | 13 +- privacy/ccpa/consentwriter_test.go | 15 +- privacy/ccpa/parsedpolicy.go | 2 +- privacy/ccpa/policy.go | 31 +- privacy/ccpa/policy_test.go | 45 +- privacy/enforcement.go | 92 - privacy/enforcement_test.go | 393 --- privacy/gdpr/consentwriter.go | 28 +- privacy/gdpr/consentwriter_test.go | 30 +- privacy/lmt/ios.go | 6 +- privacy/lmt/ios_test.go | 4 +- privacy/lmt/policy.go | 2 +- privacy/lmt/policy_test.go | 2 +- privacy/rule_condition_test.go | 4 +- privacy/scrubber.go | 331 +- privacy/scrubber_test.go | 850 ++--- privacy/writer.go | 2 +- privacy/writer_test.go | 2 +- privacysandbox/topics.go | 228 ++ privacysandbox/topics_test.go | 722 ++++ router/admin.go | 6 +- router/aspects/request_timeout_handler.go | 4 +- .../aspects/request_timeout_handler_test.go | 8 +- router/bidder_params_tests/appnexus.json | 27 + router/router.go | 188 +- router/router_test.go | 97 +- router/test_aliases.json | 12 - sample/001_banner/app.yaml | 20 + sample/001_banner/pbjs.html | 121 + sample/001_banner/stored_request.json | 28 + sample/001_banner/stored_response.json | 46 + sample/README.md | 65 + sample/docker-compose.yml | 20 + schain/schain.go | 4 +- schain/schain_test.go | 4 +- schain/schainwriter.go | 38 +- schain/schainwriter_test.go | 359 +- scripts/check_coverage.sh | 3 +- server/listener.go | 2 +- server/listener_test.go | 4 +- server/prometheus.go | 4 +- server/server.go | 26 +- server/server_test.go | 28 +- server/ssl/ssl_test.go | 2 +- static/bidder-info/33across.yaml | 5 +- static/bidder-info/adelement.yaml | 18 + static/bidder-info/adf.yaml | 8 + static/bidder-info/adkernel.yaml | 9 +- static/bidder-info/admatic.yaml | 20 + static/bidder-info/adprime.yaml | 7 + static/bidder-info/adquery.yaml | 13 +- static/bidder-info/adtonos.yaml | 24 + static/bidder-info/aidem.yaml | 4 +- static/bidder-info/algorix.yaml | 1 + static/bidder-info/alkimi.yaml | 19 + static/bidder-info/aso.yaml | 21 + static/bidder-info/axonix.yaml | 4 + static/bidder-info/bcmint.yaml | 10 + static/bidder-info/bidgency.yaml | 10 + static/bidder-info/bidmatic.yaml | 18 + static/bidder-info/bigoad.yaml | 26 + static/bidder-info/blasto.yaml | 23 + static/bidder-info/bliink.yaml | 3 + static/bidder-info/bluesea.yaml | 6 + static/bidder-info/bmtm.yaml | 6 +- static/bidder-info/boldwin.yaml | 6 +- static/bidder-info/bwx.yaml | 19 + static/bidder-info/cointraffic.yaml | 10 + static/bidder-info/concert.yaml | 18 + static/bidder-info/connectad.yaml | 21 +- static/bidder-info/consumable.yaml | 3 + static/bidder-info/conversant.yaml | 8 +- static/bidder-info/copper6ssp.yaml | 22 + static/bidder-info/cpmstar.yaml | 6 +- static/bidder-info/criteo.yaml | 6 +- static/bidder-info/displayio.yaml | 16 + static/bidder-info/driftpixel.yaml | 18 + static/bidder-info/dxkulture.yaml | 6 +- static/bidder-info/e_volution.yaml | 4 +- static/bidder-info/embimedia.yaml | 2 + static/bidder-info/epsilon.yaml | 16 +- static/bidder-info/escalax.yaml | 18 + static/bidder-info/felixads.yaml | 2 + static/bidder-info/filmzie.yaml | 1 + static/bidder-info/finative.yaml | 2 + static/bidder-info/freewheel-ssp.yaml | 15 +- static/bidder-info/freewheelssp.yaml | 5 +- static/bidder-info/grid.yaml | 2 +- static/bidder-info/gumgum.yaml | 3 + static/bidder-info/imds.yaml | 4 + static/bidder-info/indicue.yaml | 9 + static/bidder-info/inmobi.yaml | 5 +- static/bidder-info/iqx.yaml | 4 +- static/bidder-info/iqzone.yaml | 10 +- static/bidder-info/ix.yaml | 1 + static/bidder-info/jdpmedia.yaml | 5 + static/bidder-info/kargo.yaml | 7 +- static/bidder-info/krushmedia.yaml | 5 +- static/bidder-info/lemmadigital.yaml | 8 +- static/bidder-info/lockerdome.yaml | 3 + static/bidder-info/loyal.yaml | 16 + static/bidder-info/lunamedia.yaml | 2 + static/bidder-info/mabidder.yaml | 5 +- static/bidder-info/magnite.yaml | 1 + static/bidder-info/markapp.yaml | 5 + static/bidder-info/mediago.yaml | 26 + static/bidder-info/medianet.yaml | 5 +- static/bidder-info/melozen.yaml | 19 + static/bidder-info/metax.yaml | 18 + static/bidder-info/mgidX.yaml | 6 +- static/bidder-info/minutemedia.yaml | 18 + static/bidder-info/missena.yaml | 16 + static/bidder-info/mobfoxpb.yaml | 2 +- static/bidder-info/mobilefuse.yaml | 9 +- static/bidder-info/nativo.yaml | 22 + static/bidder-info/nextmillennium.yaml | 3 + static/bidder-info/nobid.yaml | 3 + static/bidder-info/oms.yaml | 11 + static/bidder-info/onetag.yaml | 5 +- static/bidder-info/openweb.yaml | 10 +- static/bidder-info/openx.yaml | 3 + static/bidder-info/oraki.yaml | 18 + static/bidder-info/ownadx.yaml | 8 +- static/bidder-info/pgamssp.yaml | 3 +- static/bidder-info/playdigo.yaml | 24 + static/bidder-info/pubmatic.yaml | 1 + static/bidder-info/pubrise.yaml | 21 + static/bidder-info/pulsepoint.yaml | 3 + static/bidder-info/qt.yaml | 19 + static/bidder-info/readpeak.yaml | 15 + static/bidder-info/relevantdigital.yaml | 17 + static/bidder-info/rise.yaml | 4 +- .../{bizzclick.yaml => roulax.yaml} | 4 +- static/bidder-info/rtbhouse.yaml | 16 + static/bidder-info/rubicon.yaml | 18 +- static/bidder-info/seedingAlliance.yaml | 2 +- static/bidder-info/sharethrough.yaml | 4 +- static/bidder-info/smaato.yaml | 2 + static/bidder-info/smarthub.yaml | 2 +- static/bidder-info/smartx.yaml | 2 + static/bidder-info/smartyads.yaml | 1 + static/bidder-info/smrtconnect.yaml | 20 + static/bidder-info/sonobi.yaml | 7 +- static/bidder-info/sovrn.yaml | 1 + static/bidder-info/sovrnXsp.yaml | 12 + static/bidder-info/streamlyn.yaml | 2 + static/bidder-info/stroeerCore.yaml | 2 + static/bidder-info/suntContent.yaml | 12 +- static/bidder-info/synacormedia.yaml | 14 +- static/bidder-info/taboola.yaml | 4 +- static/bidder-info/teads.yaml | 4 +- static/bidder-info/tgm.yaml | 1 + static/bidder-info/theadx.yaml | 23 + static/bidder-info/thetradedesk.yaml | 23 + static/bidder-info/tredio.yaml | 5 + static/bidder-info/triplelift.yaml | 8 +- static/bidder-info/triplelift_native.yaml | 6 +- static/bidder-info/trustedstack.yaml | 24 + static/bidder-info/trustx.yaml | 14 +- static/bidder-info/unruly.yaml | 7 +- static/bidder-info/vidazoo.yaml | 20 + static/bidder-info/vimayx.yaml | 2 + .../bidder-info/{liftoff.yaml => vungle.yaml} | 5 +- static/bidder-info/yahooAds.yaml | 2 +- static/bidder-info/yahooAdvertising.yaml | 14 +- static/bidder-info/yahoossp.yaml | 12 +- static/bidder-info/yandex.yaml | 13 + static/bidder-info/yieldmo.yaml | 4 +- static/bidder-info/zeta_global_ssp.yaml | 4 +- static/bidder-info/zmaticoo.yaml | 9 + static/bidder-params/adelement.json | 14 + static/bidder-params/admatic.json | 20 + static/bidder-params/adtarget.json | 5 +- static/bidder-params/adtelligent.json | 5 +- static/bidder-params/adtonos.json | 14 + static/bidder-params/alkimi.json | 28 + static/bidder-params/aso.json | 13 + static/bidder-params/bidmatic.json | 29 + static/bidder-params/bigoad.json | 13 + .../{bizzclick.json => blasto.json} | 13 +- static/bidder-params/bwx.json | 21 + static/bidder-params/cointraffic.json | 16 + static/bidder-params/concert.json | 38 + static/bidder-params/connectad.json | 46 +- static/bidder-params/consumable.json | 10 +- static/bidder-params/copper6ssp.json | 22 + static/bidder-params/displayio.json | 25 + static/bidder-params/driftpixel.json | 21 + static/bidder-params/epsilon.json | 54 - static/bidder-params/escalax.json | 22 + static/bidder-params/freewheel-ssp.json | 15 - static/bidder-params/gumgum.json | 6 +- static/bidder-params/improvedigital.json | 21 +- static/bidder-params/loyal.json | 23 + static/bidder-params/mediago.json | 24 + static/bidder-params/melozen.json | 14 + static/bidder-params/metax.json | 19 + static/bidder-params/minutemedia.json | 14 + static/bidder-params/missena.json | 24 + static/bidder-params/nativo.json | 7 + static/bidder-params/oms.json | 14 + static/bidder-params/openweb.json | 33 +- static/bidder-params/openx.json | 8 +- static/bidder-params/oraki.json | 22 + static/bidder-params/ownadx.json | 7 +- static/bidder-params/playdigo.json | 23 + static/bidder-params/pubrise.json | 22 + static/bidder-params/pulsepoint.json | 10 +- static/bidder-params/qt.json | 22 + static/bidder-params/readpeak.json | 25 + static/bidder-params/relevantdigital.json | 29 + static/bidder-params/rise.json | 16 +- static/bidder-params/roulax.json | 19 + static/bidder-params/seedingAlliance.json | 8 + static/bidder-params/smrtconnect.json | 14 + static/bidder-params/sovrnXsp.json | 27 + static/bidder-params/suntContent.json | 16 - static/bidder-params/synacormedia.json | 19 - static/bidder-params/theadx.json | 32 + static/bidder-params/thetradedesk.json | 13 + static/bidder-params/triplelift_native.json | 1 + static/bidder-params/trustedstack.json | 22 + static/bidder-params/trustx.json | 13 - static/bidder-params/vidazoo.json | 16 + .../{liftoff.json => vungle.json} | 4 +- static/bidder-params/yahooAdvertising.json | 19 - static/bidder-params/yahoossp.json | 19 - static/bidder-params/yandex.json | 33 + static/bidder-params/zmaticoo.json | 22 + static/index.html | 2 +- .../backends/db_fetcher/fetcher.go | 6 +- .../backends/db_fetcher/fetcher_test.go | 2 +- .../backends/db_provider/db_provider.go | 2 +- .../backends/db_provider/db_provider_mock.go | 2 +- .../backends/db_provider/mysql_dbprovider.go | 2 +- .../db_provider/mysql_dbprovider_test.go | 2 +- .../db_provider/postgres_dbprovider.go | 2 +- .../db_provider/postgres_dbprovider_test.go | 2 +- .../backends/empty_fetcher/fetcher.go | 4 +- .../backends/file_fetcher/fetcher.go | 14 +- .../backends/file_fetcher/fetcher_test.go | 83 +- .../test/stored_responses/bar.json | 4 + .../test/stored_responses/escaped.json | 1 + .../backends/http_fetcher/fetcher.go | 7 +- .../backends/http_fetcher/fetcher_test.go | 6 +- stored_requests/caches/cachestest/reliable.go | 2 +- stored_requests/caches/memory/cache.go | 2 +- stored_requests/caches/memory/cache_test.go | 4 +- stored_requests/config/config.go | 30 +- stored_requests/config/config_test.go | 16 +- stored_requests/data/by_id/accounts/test.json | 2 +- stored_requests/events/api/api.go | 4 +- stored_requests/events/api/api_test.go | 6 +- stored_requests/events/database/database.go | 14 +- .../events/database/database_test.go | 8 +- stored_requests/events/events.go | 2 +- stored_requests/events/events_test.go | 4 +- stored_requests/events/http/http.go | 87 +- stored_requests/events/http/http_test.go | 2 +- stored_requests/fetcher.go | 2 +- stored_requests/fetcher_test.go | 4 +- stored_responses/stored_responses.go | 134 +- stored_responses/stored_responses_test.go | 641 ++-- usersync/chooser.go | 71 +- usersync/chooser_test.go | 306 +- usersync/cookie.go | 8 +- usersync/cookie_test.go | 8 +- usersync/decoder.go | 2 +- usersync/encoder.go | 2 +- usersync/syncer.go | 36 +- usersync/syncer_test.go | 38 +- usersync/syncersbuilder.go | 8 +- usersync/syncersbuilder_test.go | 4 +- util/httputil/httputil.go | 2 +- util/httputil/httputil_test.go | 2 +- util/jsonutil/jsonutil.go | 39 +- util/jsonutil/jsonutil_test.go | 82 + util/jsonutil/merge.go | 176 + util/jsonutil/merge_test.go | 470 +++ util/maputil/maputil.go | 13 - util/maputil/maputil_test.go | 26 - util/ptrutil/ptrutil.go | 9 + util/ptrutil/ptrutil_test.go | 45 + util/sliceutil/clone.go | 12 - util/sliceutil/clone_test.go | 39 - util/stringutil/stringutil_test.go | 6 +- util/task/ticker_task_test.go | 2 +- validate.sh | 2 +- version/xprebidheader.go | 4 +- version/xprebidheader_test.go | 6 +- 3865 files changed, 117979 insertions(+), 22039 deletions(-) create mode 100644 adapters/33across/33acrosstest/supplemental/video-validation-fail-size-null.json create mode 100644 adapters/33across/33acrosstest/supplemental/video-validation-fail-size-partial.json create mode 100644 adapters/33across/33acrosstest/supplemental/video-validation-fail-size-zero.json create mode 100644 adapters/adelement/adelement.go create mode 100644 adapters/adelement/adelement_test.go create mode 100644 adapters/adelement/adelementtest/exemplary/audio-app.json create mode 100644 adapters/adelement/adelementtest/exemplary/audio-web.json create mode 100644 adapters/adelement/adelementtest/exemplary/banner-app.json create mode 100644 adapters/adelement/adelementtest/exemplary/banner-web.json rename adapters/{bizzclick/bizzclicktest => adelement/adelementtest}/exemplary/native-app.json (83%) rename adapters/{bizzclick/bizzclicktest => adelement/adelementtest}/exemplary/native-web.json (82%) create mode 100644 adapters/adelement/adelementtest/exemplary/video-app.json create mode 100644 adapters/adelement/adelementtest/exemplary/video-web.json create mode 100644 adapters/adelement/adelementtest/supplemental/empty-seatbid-array.json rename adapters/{bizzclick/bizzclicktest/supplemental/invalid-bizzclick-ext-object.json => adelement/adelementtest/supplemental/invalid-aceex-ext-object.json} (100%) rename adapters/{bizzclick/bizzclicktest => adelement/adelementtest}/supplemental/invalid-response.json (77%) rename adapters/{bizzclick/bizzclicktest => adelement/adelementtest}/supplemental/status-code-bad-request.json (87%) rename adapters/{bizzclick/bizzclicktest => adelement/adelementtest}/supplemental/status-code-no-content.json (87%) rename adapters/{bizzclick/bizzclicktest => adelement/adelementtest}/supplemental/status-code-other-error.json (83%) rename adapters/{bizzclick/bizzclicktest => adelement/adelementtest}/supplemental/status-code-service-unavailable.json (83%) create mode 100644 adapters/adhese/adhesetest/supplemental/res-no_bids_200.json rename adapters/adhese/adhesetest/supplemental/{res-no_bids.json => res-no_bids_204.json} (91%) create mode 100644 adapters/admatic/admatic.go create mode 100644 adapters/admatic/admatic_test.go create mode 100644 adapters/admatic/admatictest/exemplary/banner.json create mode 100644 adapters/admatic/admatictest/exemplary/multiple-imps.json create mode 100644 adapters/admatic/admatictest/exemplary/native.json create mode 100644 adapters/admatic/admatictest/exemplary/optional-params.json create mode 100644 adapters/admatic/admatictest/exemplary/video.json create mode 100644 adapters/admatic/admatictest/supplemental/bad-request.json create mode 100644 adapters/admatic/admatictest/supplemental/multiple-imps-with-error.json create mode 100644 adapters/admatic/admatictest/supplemental/response-200-without-body.json create mode 100644 adapters/admatic/admatictest/supplemental/response-204.json create mode 100644 adapters/admatic/admatictest/supplemental/server-error.json create mode 100644 adapters/admatic/params_test.go create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-legalName-omitted.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-legalName.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-omitted.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/invalid-regs-ext.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/site-ext.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/user-ext.json create mode 100644 adapters/adquery/adquerytest/supplemental/no-device.json create mode 100644 adapters/adquery/adquerytest/supplemental/no-site.json create mode 100644 adapters/adtonos/adtonos.go create mode 100644 adapters/adtonos/adtonos_test.go create mode 100644 adapters/adtonos/adtonostest/exemplary/simple-audio-with-mtype.json create mode 100644 adapters/adtonos/adtonostest/exemplary/simple-audio.json create mode 100644 adapters/adtonos/adtonostest/exemplary/simple-video.json create mode 100644 adapters/adtonos/adtonostest/supplemental/wrong-impression-mapping.json create mode 100644 adapters/adtonos/params_test.go create mode 100644 adapters/adview/adviewtest/exemplary/banner-app-resp-no-formattype.json create mode 100644 adapters/alkimi/alkimi.go create mode 100644 adapters/alkimi/alkimi_test.go create mode 100644 adapters/alkimi/alkimitest/exemplary/simple-audio.json create mode 100644 adapters/alkimi/alkimitest/exemplary/simple-banner.json create mode 100644 adapters/alkimi/alkimitest/exemplary/simple-video.json create mode 100644 adapters/alkimi/alkimitest/supplemental/bad_media_type.json create mode 100644 adapters/alkimi/alkimitest/supplemental/bad_response.json create mode 100644 adapters/alkimi/alkimitest/supplemental/status-204.json create mode 100644 adapters/alkimi/alkimitest/supplemental/status-not-200.json create mode 100644 adapters/alkimi/params_test.go create mode 100644 adapters/appnexus/appnexustest/supplemental/gpid.json create mode 100644 adapters/aso/aso.go create mode 100644 adapters/aso/aso_test.go create mode 100644 adapters/aso/asotest/exemplary/app-banner.json create mode 100644 adapters/aso/asotest/exemplary/app-multi-impressions.json create mode 100644 adapters/aso/asotest/exemplary/app-native.json create mode 100644 adapters/aso/asotest/exemplary/app-video.json create mode 100644 adapters/aso/asotest/exemplary/site-banner.json create mode 100644 adapters/aso/asotest/exemplary/site-native.json create mode 100644 adapters/aso/asotest/exemplary/site-video.json create mode 100644 adapters/aso/asotest/supplemental/bad-request-no-bidder.json create mode 100644 adapters/aso/asotest/supplemental/bad-request-no-ext.json create mode 100644 adapters/aso/asotest/supplemental/bad-request.json create mode 100644 adapters/aso/asotest/supplemental/empty-response.json rename adapters/{openweb/openwebtest/supplemental/status-400.json => aso/asotest/supplemental/media-type-absent.json} (52%) create mode 100644 adapters/aso/asotest/supplemental/media-type-mapping.json create mode 100644 adapters/aso/asotest/supplemental/server-error.json create mode 100644 adapters/aso/asotest/supplemental/unparsable-response.json create mode 100644 adapters/aso/params_test.go create mode 100644 adapters/beachfront/beachfronttest/supplemental/adm-video-no-size.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/adm-video-partial-size.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/adm-video-zero-size-partial.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/adm-video-zero-size.json create mode 100644 adapters/bidmatic/bidmatic.go create mode 100644 adapters/bidmatic/bidmatic_test.go create mode 100644 adapters/bidmatic/bidmatictest/exemplary/media-type-mapping.json create mode 100644 adapters/bidmatic/bidmatictest/exemplary/simple-banner.json rename adapters/{openweb/openwebtest/supplemental/status-204.json => bidmatic/bidmatictest/exemplary/simple-video.json} (78%) create mode 100644 adapters/bidmatic/bidmatictest/supplemental/explicit-dimensions.json create mode 100644 adapters/bidmatic/bidmatictest/supplemental/imp-ext-empty.json create mode 100644 adapters/bidmatic/bidmatictest/supplemental/wrong-impression-ext.json rename adapters/{openweb/openwebtest => bidmatic/bidmatictest}/supplemental/wrong-impression-mapping.json (88%) create mode 100644 adapters/bidmatic/bidmatictest/supplemental/wrong-response.json create mode 100644 adapters/bidmatic/params_test.go create mode 100644 adapters/bigoad/bigoad.go create mode 100644 adapters/bigoad/bigoad_test.go create mode 100644 adapters/bigoad/bigoadtest/exemplary/banner_app.json create mode 100644 adapters/bigoad/bigoadtest/exemplary/banner_web.json create mode 100644 adapters/bigoad/bigoadtest/exemplary/native_app.json create mode 100644 adapters/bigoad/bigoadtest/exemplary/native_web.json create mode 100644 adapters/bigoad/bigoadtest/exemplary/video_app.json create mode 100644 adapters/bigoad/bigoadtest/exemplary/video_web.json create mode 100644 adapters/bigoad/bigoadtest/supplemental/empty_seatbid.json create mode 100644 adapters/bigoad/bigoadtest/supplemental/invalid_mtype.json create mode 100644 adapters/bigoad/bigoadtest/supplemental/invalid_request.json create mode 100644 adapters/bigoad/bigoadtest/supplemental/invalid_request_bigo_ext.json create mode 100644 adapters/bigoad/bigoadtest/supplemental/invalid_response.json rename adapters/{suntContent/params_test.go => bigoad/param_test.go} (67%) rename adapters/{bizzclick/bizzclick.go => blasto/blasto.go} (81%) rename adapters/{bizzclick/bizzclick_test.go => blasto/blasto_test.go} (52%) rename adapters/{bizzclick/bizzclicktest => blasto/blastotest}/exemplary/banner-app.json (93%) rename adapters/{bizzclick/bizzclicktest => blasto/blastotest}/exemplary/banner-web.json (87%) create mode 100644 adapters/blasto/blastotest/exemplary/default-host-param.json create mode 100644 adapters/blasto/blastotest/exemplary/native-app.json create mode 100644 adapters/blasto/blastotest/exemplary/native-web.json rename adapters/{bizzclick/bizzclicktest => blasto/blastotest}/exemplary/video-app.json (92%) rename adapters/{bizzclick/bizzclicktest => blasto/blastotest}/exemplary/video-web.json (93%) rename adapters/{bizzclick/bizzclicktest => blasto/blastotest}/supplemental/empty-seatbid-array.json (89%) create mode 100644 adapters/blasto/blastotest/supplemental/invalid-blasto-ext-object.json create mode 100644 adapters/blasto/blastotest/supplemental/invalid-response.json create mode 100644 adapters/blasto/blastotest/supplemental/status-code-bad-request.json create mode 100644 adapters/blasto/blastotest/supplemental/status-code-no-content.json create mode 100644 adapters/blasto/blastotest/supplemental/status-code-other-error.json create mode 100644 adapters/blasto/blastotest/supplemental/status-code-service-unavailable.json rename adapters/{bizzclick => blasto}/params_test.go (68%) create mode 100644 adapters/bluesea/blueseatest/exemplary/site-banner.json create mode 100644 adapters/bluesea/blueseatest/exemplary/site-native.json create mode 100644 adapters/bluesea/blueseatest/exemplary/site-video.json create mode 100644 adapters/bmtm/brightmountainmediatest/exemplary/app-banner.json create mode 100644 adapters/bmtm/brightmountainmediatest/exemplary/app-video.json create mode 100644 adapters/bwx/bwx.go create mode 100644 adapters/bwx/bwx_test.go create mode 100644 adapters/bwx/bwxtest/exemplary/banner.json create mode 100644 adapters/bwx/bwxtest/exemplary/native.json create mode 100644 adapters/bwx/bwxtest/exemplary/video.json create mode 100644 adapters/bwx/bwxtest/supplemental/bad-response.json create mode 100644 adapters/bwx/bwxtest/supplemental/empty-mediatype.json create mode 100644 adapters/bwx/bwxtest/supplemental/empty-seatbid-0-bid.json create mode 100644 adapters/bwx/bwxtest/supplemental/empty-seatbid.json create mode 100644 adapters/bwx/bwxtest/supplemental/invalid-ext-bidder-object.json create mode 100644 adapters/bwx/bwxtest/supplemental/invalid-ext-object.json create mode 100644 adapters/bwx/bwxtest/supplemental/invalid-mediatype.json create mode 100644 adapters/bwx/bwxtest/supplemental/status-204.json create mode 100644 adapters/bwx/bwxtest/supplemental/status-400.json create mode 100644 adapters/bwx/bwxtest/supplemental/status-503.json create mode 100644 adapters/bwx/bwxtest/supplemental/unexpected-status.json create mode 100644 adapters/bwx/params_test.go create mode 100644 adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/bad-imp-video-missing-partial-sizes.json create mode 100644 adapters/cadent_aperture_mx/cadent_aperture_mxtest/supplemental/bad-imp-video-zero-sizes.json create mode 100644 adapters/cointraffic/cointraffic.go create mode 100644 adapters/cointraffic/cointraffic_test.go create mode 100644 adapters/cointraffic/cointraffictest/exemplary/simple-banner.json create mode 100644 adapters/cointraffic/cointraffictest/supplemental/bad-request.json create mode 100644 adapters/cointraffic/cointraffictest/supplemental/no-bid.json create mode 100644 adapters/cointraffic/cointraffictest/supplemental/server-error.json create mode 100644 adapters/cointraffic/params_test.go create mode 100644 adapters/concert/concert.go create mode 100644 adapters/concert/concert_test.go create mode 100644 adapters/concert/concerttest/exemplary/audio.json create mode 100644 adapters/concert/concerttest/exemplary/banner.json create mode 100644 adapters/concert/concerttest/exemplary/video.json create mode 100644 adapters/concert/params_test.go delete mode 100644 adapters/consumable/adtypes.go create mode 100644 adapters/consumable/consumable/exemplary/app-audio.json create mode 100644 adapters/consumable/consumable/exemplary/app-banner.json create mode 100644 adapters/consumable/consumable/exemplary/app-video.json create mode 100644 adapters/consumable/consumable/supplemental/app-audio-bad-params.json create mode 100644 adapters/consumable/consumable/supplemental/app-banner-no-ad.json create mode 100644 adapters/consumable/consumable/supplemental/app-video-no-media-type.json create mode 100644 adapters/consumable/consumable/supplemental/bad-dsp-request-example.json create mode 100644 adapters/consumable/consumable/supplemental/dsp-server-internal-error-example.json create mode 100644 adapters/consumable/consumable/supplemental/simple-banner-no-params.json create mode 100644 adapters/consumable/consumable/supplemental/unknown-status-code-example.json delete mode 100644 adapters/consumable/instant.go delete mode 100644 adapters/consumable/retrieveAd.go create mode 100644 adapters/copper6ssp/copper6ssp.go create mode 100644 adapters/copper6ssp/copper6ssp_test.go create mode 100644 adapters/copper6ssp/copper6ssptest/exemplary/endpointId.json create mode 100644 adapters/copper6ssp/copper6ssptest/exemplary/multi-format.json create mode 100644 adapters/copper6ssp/copper6ssptest/exemplary/multi-imp.json create mode 100644 adapters/copper6ssp/copper6ssptest/exemplary/simple-banner.json create mode 100644 adapters/copper6ssp/copper6ssptest/exemplary/simple-native.json create mode 100644 adapters/copper6ssp/copper6ssptest/exemplary/simple-video.json create mode 100644 adapters/copper6ssp/copper6ssptest/exemplary/simple-web-banner.json create mode 100644 adapters/copper6ssp/copper6ssptest/supplemental/bad_media_type.json create mode 100644 adapters/copper6ssp/copper6ssptest/supplemental/bad_response.json create mode 100644 adapters/copper6ssp/copper6ssptest/supplemental/no-valid-bidder-param.json create mode 100644 adapters/copper6ssp/copper6ssptest/supplemental/no-valid-imp-ext.json create mode 100644 adapters/copper6ssp/copper6ssptest/supplemental/status-204.json create mode 100644 adapters/copper6ssp/copper6ssptest/supplemental/status-not-200.json create mode 100644 adapters/copper6ssp/params_test.go create mode 100644 adapters/cpmstar/cpmstartest/exemplary/multiple-banners.json create mode 100644 adapters/criteo/criteotest/exemplary/simple-banner-paapi.json create mode 100644 adapters/criteo/criteotest/exemplary/simple-native.json create mode 100644 adapters/displayio/displayio.go create mode 100644 adapters/displayio/displayio_test.go create mode 100644 adapters/displayio/displayiotest/exemplary/multi-format.json create mode 100644 adapters/displayio/displayiotest/exemplary/multi-imp.json create mode 100644 adapters/displayio/displayiotest/exemplary/simple-banner.json create mode 100644 adapters/displayio/displayiotest/exemplary/simple-video.json create mode 100644 adapters/displayio/displayiotest/supplemental/bad-response.json create mode 100644 adapters/displayio/displayiotest/supplemental/currency-conversion.json create mode 100644 adapters/displayio/displayiotest/supplemental/ext.json create mode 100644 adapters/displayio/displayiotest/supplemental/nobid-response.json create mode 100644 adapters/displayio/displayiotest/supplemental/response-code-invalid.json create mode 100644 adapters/displayio/displayiotest/supplemental/seatbid-response.json create mode 100644 adapters/displayio/displayiotest/supplemental/unexpected-media-type.json create mode 100644 adapters/displayio/params_test.go create mode 100644 adapters/driftpixel/driftpixel.go create mode 100644 adapters/driftpixel/driftpixel_test.go create mode 100644 adapters/driftpixel/driftpixeltest/exemplary/banner.json create mode 100644 adapters/driftpixel/driftpixeltest/exemplary/multi-format.json create mode 100644 adapters/driftpixel/driftpixeltest/exemplary/multi-imp-banner.json create mode 100644 adapters/driftpixel/driftpixeltest/exemplary/native.json create mode 100644 adapters/driftpixel/driftpixeltest/exemplary/video.json create mode 100644 adapters/driftpixel/driftpixeltest/supplemental/bad-response.json create mode 100644 adapters/driftpixel/driftpixeltest/supplemental/empty-mediatype.json create mode 100644 adapters/driftpixel/driftpixeltest/supplemental/empty-seatbid-0-bid.json create mode 100644 adapters/driftpixel/driftpixeltest/supplemental/empty-seatbid.json create mode 100644 adapters/driftpixel/driftpixeltest/supplemental/invalid-ext-bidder-object.json create mode 100644 adapters/driftpixel/driftpixeltest/supplemental/invalid-ext-object.json create mode 100644 adapters/driftpixel/driftpixeltest/supplemental/invalid-mediatype.json create mode 100644 adapters/driftpixel/driftpixeltest/supplemental/status-204.json create mode 100644 adapters/driftpixel/driftpixeltest/supplemental/status-400.json create mode 100644 adapters/driftpixel/driftpixeltest/supplemental/status-503.json create mode 100644 adapters/driftpixel/driftpixeltest/supplemental/unexpected-status.json create mode 100644 adapters/driftpixel/params_test.go create mode 100644 adapters/eplanning/eplanningtest/supplemental/video-partial-size-send-640x480.json create mode 100644 adapters/eplanning/eplanningtest/supplemental/video-zero-size-send-640x480.json create mode 100644 adapters/escalax/escalax.go create mode 100644 adapters/escalax/escalax_test.go create mode 100644 adapters/escalax/escalaxtest/exemplary/banner-app.json create mode 100644 adapters/escalax/escalaxtest/exemplary/banner-web.json create mode 100644 adapters/escalax/escalaxtest/exemplary/native-app.json create mode 100644 adapters/escalax/escalaxtest/exemplary/native-web.json create mode 100644 adapters/escalax/escalaxtest/exemplary/video-app.json create mode 100644 adapters/escalax/escalaxtest/exemplary/video-web.json create mode 100644 adapters/escalax/escalaxtest/supplemental/bad_media_type.json create mode 100644 adapters/escalax/escalaxtest/supplemental/empty-seatbid-array.json create mode 100644 adapters/escalax/escalaxtest/supplemental/invalid-bidder-ext-object.json create mode 100644 adapters/escalax/escalaxtest/supplemental/invalid-ext-object.json create mode 100644 adapters/escalax/escalaxtest/supplemental/invalid-response.json create mode 100644 adapters/escalax/escalaxtest/supplemental/status-code-bad-request.json create mode 100644 adapters/escalax/escalaxtest/supplemental/status-code-other-error.json create mode 100644 adapters/escalax/params_test.go create mode 100644 adapters/flipp/flipptest/exemplary/simple-banner-native-param-transmit-eids.json create mode 100644 adapters/flipp/flipptest/exemplary/simple-banner-native-param-user-coppa.json create mode 100644 adapters/flipp/flipptest/exemplary/simple-banner-native-param-user-gdpr.json create mode 100644 adapters/gumgum/gumgumtest/supplemental/banner-with-pubId-product-params.json create mode 100644 adapters/gumgum/gumgumtest/supplemental/convert-currency.json delete mode 100644 adapters/gumgum/gumgumtest/supplemental/missing-video-params.json create mode 100644 adapters/gumgum/gumgumtest/supplemental/video-partial-size.json create mode 100644 adapters/gumgum/gumgumtest/supplemental/video-zero-size.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/nativeMultiSizesByRange.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/nativeMultiSizesByRatio.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/missing_video_size.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/zero_video_size.json delete mode 100644 adapters/improvedigital/improvedigitaltest/supplemental/addtl-consent.json create mode 100644 adapters/inmobi/inmobitest/exemplary/simple-web-native.json create mode 100644 adapters/inmobi/inmobitest/supplemental/invalid-mtype.json create mode 100644 adapters/ix/ixtest/exemplary/fledge.json create mode 100644 adapters/ix/ixtest/supplemental/bad-fledge.json create mode 100644 adapters/ix/ixtest/supplemental/dsa-request.json create mode 100644 adapters/ix/ixtest/supplemental/fledge-no-bid.json delete mode 100644 adapters/liftoff/liftoff_test.go create mode 100644 adapters/loyal/loyal.go create mode 100644 adapters/loyal/loyal_test.go create mode 100644 adapters/loyal/loyaltest/exemplary/endpointId.json create mode 100644 adapters/loyal/loyaltest/exemplary/multi-format.json create mode 100644 adapters/loyal/loyaltest/exemplary/multi-imp.json create mode 100644 adapters/loyal/loyaltest/exemplary/simple-banner.json create mode 100644 adapters/loyal/loyaltest/exemplary/simple-native.json create mode 100644 adapters/loyal/loyaltest/exemplary/simple-video.json create mode 100644 adapters/loyal/loyaltest/exemplary/simple-web-banner.json create mode 100644 adapters/loyal/loyaltest/supplemental/bad_media_type.json create mode 100644 adapters/loyal/loyaltest/supplemental/bad_response.json create mode 100644 adapters/loyal/loyaltest/supplemental/no-valid-impressions.json create mode 100644 adapters/loyal/loyaltest/supplemental/status-204.json create mode 100644 adapters/loyal/loyaltest/supplemental/status-not-200.json create mode 100644 adapters/loyal/params_test.go create mode 100644 adapters/mediago/mediago.go create mode 100644 adapters/mediago/mediago_test.go create mode 100644 adapters/mediago/mediagotest/exemplary/sample-banner-apac.json create mode 100644 adapters/mediago/mediagotest/exemplary/sample-banner-euc.json create mode 100644 adapters/mediago/mediagotest/exemplary/sample-banner-fallback-to-first-imp-to-get-ep.json create mode 100644 adapters/mediago/mediagotest/exemplary/sample-banner-use.json create mode 100644 adapters/mediago/mediagotest/exemplary/sample-banner.json create mode 100644 adapters/mediago/mediagotest/exemplary/sample-native.json create mode 100644 adapters/mediago/mediagotest/exemplary/sample-nobid.json create mode 100644 adapters/mediago/mediagotest/exemplary/sample-with-mtype.json create mode 100644 adapters/mediago/mediagotest/supplemental/bad_400_reponse.json create mode 100644 adapters/mediago/mediagotest/supplemental/bad_500_reponse.json create mode 100644 adapters/mediago/mediagotest/supplemental/bad_imp_ext.json create mode 100644 adapters/mediago/mediagotest/supplemental/bad_impext_bidder.json create mode 100644 adapters/mediago/mediagotest/supplemental/bad_request_no_token.json create mode 100644 adapters/mediago/mediagotest/supplemental/bad_response.json create mode 100644 adapters/mediago/mediagotest/supplemental/bad_response_error_mtype.json create mode 100644 adapters/mediago/params_test.go create mode 100644 adapters/melozen/melozen.go create mode 100644 adapters/melozen/melozen_test.go create mode 100644 adapters/melozen/melozentest/exemplary/app-banner.json create mode 100644 adapters/melozen/melozentest/exemplary/app-native.json create mode 100644 adapters/melozen/melozentest/exemplary/app-video.json create mode 100644 adapters/melozen/melozentest/exemplary/multi-imps.json create mode 100644 adapters/melozen/melozentest/exemplary/web-banner.json create mode 100644 adapters/melozen/melozentest/exemplary/web-video.json create mode 100644 adapters/melozen/melozentest/supplemental/bad-media-type-request.json create mode 100644 adapters/melozen/melozentest/supplemental/no-fill.json create mode 100644 adapters/melozen/melozentest/supplemental/response-status-400.json create mode 100644 adapters/melozen/melozentest/supplemental/response-status-not-200.json create mode 100644 adapters/melozen/melozentest/supplemental/wrong-bid-ext.json create mode 100644 adapters/melozen/params_test.go create mode 100644 adapters/metax/metax.go create mode 100644 adapters/metax/metax_test.go create mode 100644 adapters/metax/metaxtest/exemplary/app-formats.json create mode 100644 adapters/metax/metaxtest/exemplary/app-imps.json create mode 100644 adapters/metax/metaxtest/exemplary/no-bid.json create mode 100644 adapters/metax/metaxtest/exemplary/no-seat-bid.json create mode 100644 adapters/metax/metaxtest/exemplary/no-seat.json create mode 100644 adapters/metax/metaxtest/exemplary/simple-app-audio.json create mode 100644 adapters/metax/metaxtest/exemplary/simple-app-banner.json create mode 100644 adapters/metax/metaxtest/exemplary/simple-app-native.json create mode 100644 adapters/metax/metaxtest/exemplary/simple-app-video.json create mode 100644 adapters/metax/metaxtest/exemplary/simple-site-audio.json create mode 100644 adapters/metax/metaxtest/exemplary/simple-site-banner.json create mode 100644 adapters/metax/metaxtest/exemplary/simple-site-native.json create mode 100644 adapters/metax/metaxtest/exemplary/simple-site-video.json create mode 100644 adapters/metax/metaxtest/supplemental/invalid-adunit-error.json create mode 100644 adapters/metax/metaxtest/supplemental/invalid-ext-bidder.json create mode 100644 adapters/metax/metaxtest/supplemental/invalid-ext.json create mode 100644 adapters/metax/metaxtest/supplemental/invalid-publisher-error.json create mode 100644 adapters/metax/metaxtest/supplemental/resp-bad-json.json create mode 100644 adapters/metax/metaxtest/supplemental/resp-bad-markuptype.json create mode 100644 adapters/metax/metaxtest/supplemental/status-400.json create mode 100644 adapters/metax/metaxtest/supplemental/status-500.json create mode 100644 adapters/metax/metaxtest/supplemental/status-503.json create mode 100644 adapters/metax/params_test.go create mode 100644 adapters/minutemedia/minutemedia.go create mode 100644 adapters/minutemedia/minutemedia_test.go create mode 100644 adapters/minutemedia/minutemediatest/exemplary/banner-and-video-app.json create mode 100644 adapters/minutemedia/minutemediatest/exemplary/banner-and-video-gdpr.json create mode 100644 adapters/minutemedia/minutemediatest/exemplary/banner-and-video-site.json create mode 100644 adapters/minutemedia/minutemediatest/exemplary/banner-and-video.json create mode 100644 adapters/minutemedia/minutemediatest/exemplary/simple-banner.json create mode 100644 adapters/minutemedia/minutemediatest/exemplary/simple-video.json create mode 100644 adapters/minutemedia/minutemediatest/supplemental/bad-request.json create mode 100644 adapters/minutemedia/minutemediatest/supplemental/missing-bidder.json create mode 100644 adapters/minutemedia/minutemediatest/supplemental/missing-extension.json create mode 100644 adapters/minutemedia/minutemediatest/supplemental/missing-mtype.json create mode 100644 adapters/missena/missena.go create mode 100644 adapters/missena/missena_test.go create mode 100644 adapters/missena/missenatest/exemplary/multiple-imps.json create mode 100644 adapters/missena/missenatest/exemplary/simple-banner-ipv6.json create mode 100644 adapters/missena/missenatest/exemplary/simple-banner.json create mode 100644 adapters/missena/missenatest/exemplary/valid-imp-error-imp.json create mode 100644 adapters/missena/missenatest/supplemental/error-ext-bidder.json create mode 100644 adapters/missena/missenatest/supplemental/error-imp-ext.json create mode 100644 adapters/missena/missenatest/supplemental/status-204.json create mode 100644 adapters/missena/missenatest/supplemental/status-400.json create mode 100644 adapters/missena/missenatest/supplemental/status-not-200.json create mode 100644 adapters/missena/params_test.go create mode 100644 adapters/nativo/nativo.go create mode 100644 adapters/nativo/nativo_test.go create mode 100644 adapters/nativo/nativotest/exemplary/banner-app.json create mode 100644 adapters/nativo/nativotest/exemplary/banner-web.json create mode 100644 adapters/nativo/nativotest/exemplary/native-app.json create mode 100644 adapters/nativo/nativotest/exemplary/native-web.json create mode 100644 adapters/nativo/nativotest/exemplary/video-app.json create mode 100644 adapters/nativo/nativotest/exemplary/video-web.json create mode 100755 adapters/nativo/nativotest/supplemental/200-different-impID-response-from-nativo.json create mode 100755 adapters/nativo/nativotest/supplemental/204-response-from-nativo.json create mode 100755 adapters/nativo/nativotest/supplemental/400-response-from-nativo.json create mode 100755 adapters/nativo/nativotest/supplemental/500-response-from-nativo.json create mode 100644 adapters/nextmillennium/nextmillenniumtest/exemplary/video.json create mode 100644 adapters/oms/oms.go create mode 100644 adapters/oms/oms_test.go create mode 100755 adapters/oms/omstest/exemplary/simple-banner-cookie-uid.json create mode 100644 adapters/oms/omstest/exemplary/simple-banner-multiple-bids.json create mode 100755 adapters/oms/omstest/exemplary/simple-banner-uid.json create mode 100644 adapters/oms/omstest/exemplary/simple-multi-type-banner.json create mode 100755 adapters/oms/omstest/supplemental/204-response-from-target.json create mode 100755 adapters/oms/omstest/supplemental/400-response-from-target.json create mode 100755 adapters/oms/omstest/supplemental/500-response-from-target.json create mode 100755 adapters/oms/omstest/supplemental/simple-banner-with-ipv6.json create mode 100644 adapters/oms/params_test.go delete mode 100644 adapters/openweb/openwebtest/exemplary/multiple-imps-same-aid.json create mode 100644 adapters/openweb/openwebtest/supplemental/missing-mtype.json rename adapters/openweb/openwebtest/supplemental/{no-valid-imps.json => missing-org-and-aid.json} (78%) create mode 100644 adapters/openweb/openwebtest/supplemental/missing-placement-id.json delete mode 100644 adapters/openweb/openwebtest/supplemental/status-500.json create mode 100644 adapters/oraki/oraki.go create mode 100644 adapters/oraki/oraki_test.go create mode 100644 adapters/oraki/orakitest/exemplary/endpointId.json create mode 100644 adapters/oraki/orakitest/exemplary/multi-format.json create mode 100644 adapters/oraki/orakitest/exemplary/multi-imp.json create mode 100644 adapters/oraki/orakitest/exemplary/simple-banner.json create mode 100644 adapters/oraki/orakitest/exemplary/simple-native.json create mode 100644 adapters/oraki/orakitest/exemplary/simple-video.json create mode 100644 adapters/oraki/orakitest/exemplary/simple-web-banner.json create mode 100644 adapters/oraki/orakitest/supplemental/bad_media_type.json create mode 100644 adapters/oraki/orakitest/supplemental/bad_response.json create mode 100644 adapters/oraki/orakitest/supplemental/status-204.json create mode 100644 adapters/oraki/orakitest/supplemental/status-not-200.json create mode 100644 adapters/oraki/params_test.go create mode 100644 adapters/pgamssp/pgamssptest/exemplary/convert_currency.json create mode 100644 adapters/playdigo/params_test.go create mode 100644 adapters/playdigo/playdigo.go create mode 100644 adapters/playdigo/playdigo_test.go create mode 100644 adapters/playdigo/playdigotest/exemplary/endpointId.json create mode 100644 adapters/playdigo/playdigotest/exemplary/multi-format.json create mode 100644 adapters/playdigo/playdigotest/exemplary/multi-imp.json create mode 100644 adapters/playdigo/playdigotest/exemplary/simple-banner.json create mode 100644 adapters/playdigo/playdigotest/exemplary/simple-native.json create mode 100644 adapters/playdigo/playdigotest/exemplary/simple-video.json create mode 100644 adapters/playdigo/playdigotest/exemplary/simple-web-banner.json create mode 100644 adapters/playdigo/playdigotest/supplemental/bad_media_type.json create mode 100644 adapters/playdigo/playdigotest/supplemental/bad_response.json create mode 100644 adapters/playdigo/playdigotest/supplemental/no-valid-impressions.json create mode 100644 adapters/playdigo/playdigotest/supplemental/status-204.json create mode 100644 adapters/playdigo/playdigotest/supplemental/status-not-200.json rename adapters/pubmatic/pubmatictest/supplemental/{impExtData.json => impExt.json} (96%) create mode 100644 adapters/pubrise/params_test.go create mode 100644 adapters/pubrise/pubrise.go create mode 100644 adapters/pubrise/pubrise_test.go create mode 100644 adapters/pubrise/pubrisetest/exemplary/endpointId.json create mode 100644 adapters/pubrise/pubrisetest/exemplary/multi-format.json create mode 100644 adapters/pubrise/pubrisetest/exemplary/multi-imp.json create mode 100644 adapters/pubrise/pubrisetest/exemplary/simple-banner.json create mode 100644 adapters/pubrise/pubrisetest/exemplary/simple-native.json create mode 100644 adapters/pubrise/pubrisetest/exemplary/simple-video.json create mode 100644 adapters/pubrise/pubrisetest/exemplary/simple-web-banner.json create mode 100644 adapters/pubrise/pubrisetest/supplemental/bad_media_type.json create mode 100644 adapters/pubrise/pubrisetest/supplemental/bad_response.json create mode 100644 adapters/pubrise/pubrisetest/supplemental/no-valid-impressions.json create mode 100644 adapters/pubrise/pubrisetest/supplemental/status-204.json create mode 100644 adapters/pubrise/pubrisetest/supplemental/status-not-200.json create mode 100644 adapters/pulsepoint/pulsepointtest/exemplary/banner-string-bidder-params.json create mode 100644 adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid-params-missing-param.json create mode 100644 adapters/qt/params_test.go create mode 100644 adapters/qt/qt.go create mode 100644 adapters/qt/qt_test.go create mode 100644 adapters/qt/qttest/exemplary/endpointId.json create mode 100644 adapters/qt/qttest/exemplary/multi-format.json create mode 100644 adapters/qt/qttest/exemplary/multi-imp.json create mode 100644 adapters/qt/qttest/exemplary/simple-banner.json create mode 100644 adapters/qt/qttest/exemplary/simple-native.json create mode 100644 adapters/qt/qttest/exemplary/simple-video.json create mode 100644 adapters/qt/qttest/exemplary/simple-web-banner.json create mode 100644 adapters/qt/qttest/supplemental/bad_media_type.json create mode 100644 adapters/qt/qttest/supplemental/bad_response.json create mode 100644 adapters/qt/qttest/supplemental/status-204.json create mode 100644 adapters/qt/qttest/supplemental/status-not-200.json create mode 100644 adapters/readpeak/params_test.go create mode 100644 adapters/readpeak/readpeak.go create mode 100644 adapters/readpeak/readpeak_test.go create mode 100644 adapters/readpeak/readpeaktest/exemplary/banner.json create mode 100644 adapters/readpeak/readpeaktest/exemplary/banner_app.json create mode 100644 adapters/readpeak/readpeaktest/exemplary/banner_multiple_imps.json create mode 100644 adapters/readpeak/readpeaktest/exemplary/native.json create mode 100644 adapters/readpeak/readpeaktest/supplemental/banner_without_mtype.json create mode 100644 adapters/readpeak/readpeaktest/supplemental/bid_response_204.json create mode 100644 adapters/readpeak/readpeaktest/supplemental/bid_response_400.json create mode 100644 adapters/readpeak/readpeaktest/supplemental/bid_response_500.json create mode 100644 adapters/relevantdigital/params_test.go create mode 100644 adapters/relevantdigital/relevantdigital.go create mode 100644 adapters/relevantdigital/relevantdigital_test.go create mode 100644 adapters/relevantdigital/relevantdigitaltest/exemplary/simple-audio.json create mode 100644 adapters/relevantdigital/relevantdigitaltest/exemplary/simple-banner.json create mode 100644 adapters/relevantdigital/relevantdigitaltest/exemplary/simple-native.json create mode 100644 adapters/relevantdigital/relevantdigitaltest/exemplary/simple-video.json create mode 100644 adapters/relevantdigital/relevantdigitaltest/supplemental/invalidBidMType.json create mode 100644 adapters/relevantdigital/relevantdigitaltest/supplemental/invalidBidMTypeParsesExt.json create mode 100644 adapters/relevantdigital/relevantdigitaltest/supplemental/invalidBidType.json create mode 100644 adapters/relevantdigital/relevantdigitaltest/supplemental/invalidParam.json create mode 100644 adapters/relevantdigital/relevantdigitaltest/supplemental/invalidRequestCount.json create mode 100644 adapters/richaudience/richaudiencetest/supplemental/videoSizePartial.json create mode 100644 adapters/richaudience/richaudiencetest/supplemental/videoSizeZero.json create mode 100644 adapters/roulax/roulax.go create mode 100644 adapters/roulax/roulax_test.go create mode 100644 adapters/roulax/roulaxtest/exemplary/simple-banner.json create mode 100644 adapters/roulax/roulaxtest/exemplary/simple-native.json create mode 100644 adapters/roulax/roulaxtest/exemplary/simple-video.json create mode 100644 adapters/roulax/roulaxtest/supplemental/no-bid-response.json create mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/app_banner.json create mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/app_native.json create mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/banner-resolve-macros.json create mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param-without-cur.json create mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-bidder-param.json create mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-with-cur.json create mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/bidfloor-as-impbidfloor-without-cur.json delete mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/currency-conversion.json create mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/native-with-deprecated-native-prop.json create mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/native-with-proper-native-response.json create mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/two-bidfloors-given-param-and-impbidfloor.json create mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/banner-native-req-faulty-mtype-in-native.json create mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-bidder-params.json create mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/faulty-request-no-impext.json create mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-native-prop.json create mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/native-with-faulty-adm-response.json create mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-bad-mtype.json create mode 100644 adapters/rtbhouse/rtbhousetest/supplemental/simple-banner-no-mtype.json create mode 100644 adapters/seedingAlliance/seedingAlliancetest/exemplary/banner_with_account.json create mode 100644 adapters/seedingAlliance/seedingAlliancetest/exemplary/banner_with_account_and_seat.json rename adapters/{suntContent/suntContenttest/exemplary/banner.json => seedingAlliance/seedingAlliancetest/exemplary/banner_with_seat.json} (94%) create mode 100644 adapters/smaato/banner.go create mode 100644 adapters/smaato/banner_test.go delete mode 100644 adapters/smaato/image.go delete mode 100644 adapters/smaato/image_test.go delete mode 100644 adapters/smaato/richmedia.go delete mode 100644 adapters/smaato/richmedia_test.go rename adapters/smaato/smaatotest/exemplary/{simple-banner-richMedia-app.json => simple-banner-dooh.json} (78%) rename adapters/smaato/smaatotest/{supplemental/adtype-header-response.json => exemplary/simple-banner-eids.json} (81%) create mode 100644 adapters/smaato/smaatotest/exemplary/video-dooh.json delete mode 100644 adapters/smaato/smaatotest/supplemental/bad-adm-response.json delete mode 100644 adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-request.json rename adapters/smaato/smaatotest/{exemplary/simple-banner-richMedia.json => supplemental/curl-nil-response.json} (81%) rename adapters/smaato/smaatotest/supplemental/{no-app-site-request.json => no-app-site-dooh-request.json} (92%) rename adapters/smaato/smaatotest/videosupplemental/{bad-adm-response.json => bad-adtype-header-response.json} (77%) create mode 100644 adapters/smrtconnect/smrtconnect.go create mode 100644 adapters/smrtconnect/smrtconnect_test.go create mode 100644 adapters/smrtconnect/smrtconnecttest/exemplary/audio-app.json create mode 100644 adapters/smrtconnect/smrtconnecttest/exemplary/audio-web.json create mode 100644 adapters/smrtconnect/smrtconnecttest/exemplary/banner-app.json create mode 100644 adapters/smrtconnect/smrtconnecttest/exemplary/banner-multiple-bids.json create mode 100644 adapters/smrtconnect/smrtconnecttest/exemplary/banner-web.json create mode 100644 adapters/smrtconnect/smrtconnecttest/exemplary/native-app.json create mode 100644 adapters/smrtconnect/smrtconnecttest/exemplary/native-web.json create mode 100644 adapters/smrtconnect/smrtconnecttest/exemplary/video-app.json create mode 100644 adapters/smrtconnect/smrtconnecttest/exemplary/video-web.json create mode 100644 adapters/smrtconnect/smrtconnecttest/supplemental/empty-seatbid-array.json create mode 100644 adapters/smrtconnect/smrtconnecttest/supplemental/invalid-aceex-ext-object.json create mode 100644 adapters/smrtconnect/smrtconnecttest/supplemental/invalid-response.json create mode 100644 adapters/smrtconnect/smrtconnecttest/supplemental/status-code-bad-request.json create mode 100644 adapters/smrtconnect/smrtconnecttest/supplemental/status-code-no-content.json create mode 100644 adapters/smrtconnect/smrtconnecttest/supplemental/status-code-other-error.json create mode 100644 adapters/smrtconnect/smrtconnecttest/supplemental/status-code-service-unavailable.json create mode 100644 adapters/sonobi/sonobitest/exemplary/native.json create mode 100644 adapters/sonobi/sonobitest/supplemental/currency-conversion.json create mode 100644 adapters/sovrnXsp/params_test.go create mode 100644 adapters/sovrnXsp/sovrnXsp.go create mode 100644 adapters/sovrnXsp/sovrnXsp_test.go create mode 100644 adapters/sovrnXsp/sovrnXsptest/exemplary/banner.json create mode 100644 adapters/sovrnXsp/sovrnXsptest/exemplary/native.json create mode 100644 adapters/sovrnXsp/sovrnXsptest/exemplary/video.json create mode 100644 adapters/sovrnXsp/sovrnXsptest/supplemental/request-no-matching-imp.json create mode 100644 adapters/sovrnXsp/sovrnXsptest/supplemental/response-empty-seat.json create mode 100644 adapters/sovrnXsp/sovrnXsptest/supplemental/response-http-error.json create mode 100644 adapters/sovrnXsp/sovrnXsptest/supplemental/response-invalid-crtype.json create mode 100644 adapters/sovrnXsp/sovrnXsptest/supplemental/response-nobid.json create mode 100644 adapters/stroeerCore/stroeercoretest/exemplary/dsa.json create mode 100644 adapters/stroeerCore/stroeercoretest/exemplary/site-multi-format-single.json create mode 100644 adapters/stroeerCore/stroeercoretest/exemplary/site-multi-types.json create mode 100644 adapters/stroeerCore/stroeercoretest/exemplary/site-video-single.json create mode 100644 adapters/stroeerCore/stroeercoretest/supplemental/unknown-bid-media-type.json delete mode 100644 adapters/suntContent/suntContent.go delete mode 100644 adapters/suntContent/suntContent_test.go delete mode 100644 adapters/suntContent/suntContenttest/exemplary/native.json delete mode 100644 adapters/suntContent/suntContenttest/supplemental/invalid_tag_id.json delete mode 100644 adapters/suntContent/suntContenttest/supplemental/status_bad_request.json rename adapters/taboola/taboolatest/{supplemental/emptySiteInRequest.json => exemplary/bannerAppRequest.json} (92%) create mode 100644 adapters/theadx/params_test.go create mode 100644 adapters/theadx/theadx.go create mode 100644 adapters/theadx/theadx_test.go create mode 100644 adapters/theadx/theadxtest/exemplary/dynamic-tag.json create mode 100644 adapters/theadx/theadxtest/exemplary/multi-format.json create mode 100644 adapters/theadx/theadxtest/exemplary/multi-native.json create mode 100644 adapters/theadx/theadxtest/exemplary/single-banner.json create mode 100644 adapters/theadx/theadxtest/exemplary/single-native.json create mode 100644 adapters/theadx/theadxtest/exemplary/single-video.json create mode 100644 adapters/theadx/theadxtest/supplemental/bad-request.json create mode 100644 adapters/theadx/theadxtest/supplemental/empty-response.json create mode 100644 adapters/theadx/theadxtest/supplemental/nobid-response.json create mode 100644 adapters/theadx/theadxtest/supplemental/server-error.json create mode 100644 adapters/theadx/theadxtest/supplemental/unparsable-response.json create mode 100644 adapters/thetradedesk/params_test.go create mode 100644 adapters/thetradedesk/thetradedesk.go create mode 100644 adapters/thetradedesk/thetradedesk_test.go create mode 100644 adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-inapp.json create mode 100644 adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids-and-formats.json create mode 100644 adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids.json create mode 100644 adapters/thetradedesk/thetradedesktest/exemplary/simple-banner.json create mode 100644 adapters/thetradedesk/thetradedesktest/exemplary/simple-empty-publisherId.json create mode 100644 adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-banner.json create mode 100644 adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-video.json create mode 100644 adapters/thetradedesk/thetradedesktest/exemplary/simple-native.json create mode 100644 adapters/thetradedesk/thetradedesktest/exemplary/simple-video.json create mode 100644 adapters/thetradedesk/thetradedesktest/supplemental/200-response-from-target.json create mode 100644 adapters/thetradedesk/thetradedesktest/supplemental/204-response-from-target.json create mode 100644 adapters/thetradedesk/thetradedesktest/supplemental/400-response-from-target.json create mode 100644 adapters/thetradedesk/thetradedesktest/supplemental/500-response-from-target.json create mode 100644 adapters/thetradedesk/thetradedesktest/supplemental/invalid-mtype.json create mode 100644 adapters/thetradedesk/thetradedesktest/supplemental/invalid-publisher.json create mode 100644 adapters/triplelift_native/params_test.go create mode 100644 adapters/triplelift_native/triplelift_nativetest/exemplary/app-msn-no-tag-code.json create mode 100644 adapters/triplelift_native/triplelift_nativetest/exemplary/app-msn.json create mode 100644 adapters/triplelift_native/triplelift_nativetest/exemplary/app.json delete mode 100644 adapters/triplelift_native/triplelift_nativetest/exemplary/optional-params.json create mode 100644 adapters/triplelift_native/triplelift_nativetest/exemplary/site-msn-no-tag-code.json create mode 100644 adapters/triplelift_native/triplelift_nativetest/exemplary/site-msn.json create mode 100644 adapters/triplelift_native/triplelift_nativetest/exemplary/site.json create mode 100644 adapters/triplelift_native/triplelift_nativetest/supplemental/app-no-publisher.json create mode 100644 adapters/triplelift_native/triplelift_nativetest/supplemental/app-publisher-no-domain.json create mode 100644 adapters/triplelift_native/triplelift_nativetest/supplemental/effective-publisher-allowed.json create mode 100644 adapters/triplelift_native/triplelift_nativetest/supplemental/effective-publisher-not-allowed.json create mode 100644 adapters/triplelift_native/triplelift_nativetest/supplemental/no-imp-ext-data.json delete mode 100644 adapters/triplelift_native/triplelift_nativetest/supplemental/notgoodstatuscode.json create mode 100644 adapters/triplelift_native/triplelift_nativetest/supplemental/site-no-publisher.json create mode 100644 adapters/triplelift_native/triplelift_nativetest/supplemental/site-publisher-no-domain.json create mode 100644 adapters/trustedstack/params_test.go create mode 100644 adapters/trustedstack/trustedstack.go create mode 100644 adapters/trustedstack/trustedstack_test.go create mode 100644 adapters/trustedstack/trustedstacktest/exemplary/multi-format.json create mode 100644 adapters/trustedstack/trustedstacktest/exemplary/multi-imps.json rename adapters/{suntContent/suntContenttest/supplemental/status_no_content.json => trustedstack/trustedstacktest/exemplary/no-bid.json} (61%) rename adapters/{suntContent/suntContenttest/supplemental/status_not_ok.json => trustedstack/trustedstacktest/exemplary/optional-params.json} (56%) create mode 100644 adapters/trustedstack/trustedstacktest/exemplary/simple-banner.json create mode 100644 adapters/trustedstack/trustedstacktest/exemplary/simple-native.json create mode 100644 adapters/trustedstack/trustedstacktest/exemplary/simple-video.json create mode 100644 adapters/trustedstack/trustedstacktest/supplemental/invalid-req-400-status-code-bad-request.json create mode 100644 adapters/trustedstack/trustedstacktest/supplemental/invalid-req-500-status-code-bad-request.json create mode 100644 adapters/trustedstack/trustedstacktest/supplemental/ivalid-req-200-incorrect-response-mtype.json create mode 100644 adapters/trustedstack/trustedstacktest/supplemental/valid-req-200-bid-response-from-trustedstack.json create mode 100644 adapters/trustedstack/trustedstacktest/supplemental/valid-req-200-incorrect-response-format.json create mode 100755 adapters/trustedstack/trustedstacktest/supplemental/valid-req-204-response-from-trustedstack.json create mode 100644 adapters/vidazoo/params_test.go create mode 100644 adapters/vidazoo/vidazoo.go create mode 100644 adapters/vidazoo/vidazoo_test.go create mode 100644 adapters/vidazoo/vidazootest/exemplary/banner.json create mode 100644 adapters/vidazoo/vidazootest/exemplary/multi-imp.json create mode 100644 adapters/vidazoo/vidazootest/exemplary/video.json create mode 100644 adapters/vidazoo/vidazootest/supplemental/bad-request.json create mode 100644 adapters/vidazoo/vidazootest/supplemental/internal-error.json create mode 100644 adapters/vidazoo/vidazootest/supplemental/no-content.json create mode 100644 adapters/vidazoo/vidazootest/supplemental/unknown-bid-type.json rename adapters/{liftoff => vungle}/param_test.go (78%) rename adapters/{liftoff/liftoff.go => vungle/vungle.go} (69%) create mode 100644 adapters/vungle/vungle_test.go rename adapters/{liftoff/liftofftest => vungle/vungletest}/exemplary/app_video_instl.json (95%) rename adapters/{liftoff/liftofftest => vungle/vungletest}/exemplary/app_video_rewarded.json (95%) create mode 100644 adapters/vungle/vungletest/exemplary/site_video_instl.json create mode 100644 adapters/vungle/vungletest/exemplary/site_video_rewarded.json rename adapters/{liftoff/liftofftest => vungle/vungletest}/supplemental/appid_placementid_check.json (95%) rename adapters/{liftoff/liftofftest => vungle/vungletest}/supplemental/missing_appid_or_placementid.json (96%) create mode 100644 adapters/vungle/vungletest/supplemental/no_site_or_app_video_rewarded.json rename adapters/{liftoff/liftofftest => vungle/vungletest}/supplemental/response_code_204.json (96%) rename adapters/{liftoff/liftofftest => vungle/vungletest}/supplemental/response_code_400.json (96%) rename adapters/{liftoff/liftofftest => vungle/vungletest}/supplemental/response_code_non_200.json (96%) rename adapters/{liftoff/liftofftest/supplemental/liftoff_ext_check.json => vungle/vungletest/supplemental/vungle_ext_check.json} (95%) create mode 100644 adapters/yandex/params_test.go create mode 100644 adapters/yandex/yandex.go create mode 100644 adapters/yandex/yandex_test.go create mode 100644 adapters/yandex/yandextest/exemplary/native.json create mode 100644 adapters/yandex/yandextest/exemplary/simple-banner.json create mode 100644 adapters/yandex/yandextest/supplemental/multiple-imps-some-malformed.json create mode 100644 adapters/yandex/yandextest/supplemental/multiple-imps.json create mode 100644 adapters/yandex/yandextest/supplemental/simple-banner-empty-response.json create mode 100644 adapters/yandex/yandextest/supplemental/simple-banner-empty-seatbid.json create mode 100644 adapters/yandex/yandextest/supplemental/simple-banner-sizes.json create mode 100644 adapters/yandex/yandextest/supplemental/simple-banner-status-400.json create mode 100644 adapters/yandex/yandextest/supplemental/simple-banner-unknown-imp.json create mode 100644 adapters/yandex/yandextest/supplemental/simple-banner-unparsable-body.json create mode 100644 adapters/yandex/yandextest/supplemental/unknown-banner.json create mode 100644 adapters/yieldlab/yieldlabtest/exemplary/dsa.json create mode 100644 adapters/yieldlab/yieldlabtest/supplemental/dsa_empty.json create mode 100644 adapters/yieldlab/yieldlabtest/supplemental/dsa_empty_transparency.json create mode 100644 adapters/yieldlab/yieldlabtest/supplemental/invalid_reg_ext.json create mode 100644 adapters/yieldmo/yieldmotest/exemplary/valid_currency_conversion.json create mode 100644 adapters/yieldmo/yieldmotest/supplemental/unsupported_currency.json create mode 100644 adapters/zmaticoo/params_test.go create mode 100644 adapters/zmaticoo/zmaticoo.go create mode 100644 adapters/zmaticoo/zmaticoo_test.go create mode 100644 adapters/zmaticoo/zmaticootest/exemplary/no-bid.json create mode 100644 adapters/zmaticoo/zmaticootest/exemplary/simple-banner.json create mode 100644 adapters/zmaticoo/zmaticootest/exemplary/simple-native-1.1.json create mode 100644 adapters/zmaticoo/zmaticootest/exemplary/simple-native.json create mode 100644 adapters/zmaticoo/zmaticootest/exemplary/simple-video.json create mode 100644 adapters/zmaticoo/zmaticootest/supplemental/bad_imp_ext.json create mode 100644 adapters/zmaticoo/zmaticootest/supplemental/bad_imp_ext_bidder.json create mode 100644 adapters/zmaticoo/zmaticootest/supplemental/bad_response.json rename adapters/{triplelift_native/triplelift_nativetest/supplemental/nopub.json => zmaticoo/zmaticootest/supplemental/empty_imp_ext_bidder.json} (52%) create mode 100644 adapters/zmaticoo/zmaticootest/supplemental/status_400.json create mode 100644 adapters/zmaticoo/zmaticootest/supplemental/status_500.json create mode 100644 analytics/agma/README.md create mode 100644 analytics/agma/agma_module.go create mode 100644 analytics/agma/agma_module_test.go create mode 100644 analytics/agma/model.go create mode 100644 analytics/agma/model_test.go create mode 100644 analytics/agma/sender.go create mode 100644 analytics/agma/sender_test.go create mode 100644 currency/currency.go create mode 100644 currency/currency_mock.go create mode 100644 currency/currency_test.go create mode 100644 docs/build/README.md delete mode 100644 docs/developers/features.md delete mode 100644 docs/endpoints.md create mode 100644 dsa/validate.go create mode 100644 dsa/validate_test.go create mode 100644 dsa/writer.go create mode 100644 dsa/writer_test.go rename endpoints/openrtb2/sample-requests/aliased/{simple.json => default-request-alias.json} (76%) create mode 100644 endpoints/openrtb2/sample-requests/aliased/hard-alias.json create mode 100644 endpoints/openrtb2/sample-requests/aliased/request-alias.json create mode 100644 endpoints/openrtb2/sample-requests/alternate-bidder-code/alternate-bidder-code.json create mode 100644 endpoints/openrtb2/sample-requests/amp/valid-supplementary/ortb-2.5-to-2.6-upconvert.json rename endpoints/openrtb2/sample-requests/{blacklisted/blacklisted-app.json => blocked/blocked-app.json} (96%) rename endpoints/openrtb2/sample-requests/invalid-whole/{unknown-bidder.json => imp-ext-prebid-bidder-empty.json} (80%) create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/invalid-bidder-params.json delete mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json rename endpoints/openrtb2/sample-requests/invalid-whole/{regs-ext-gdpr-invalid.json => regs-gdpr-invalid.json} (86%) rename endpoints/openrtb2/sample-requests/invalid-whole/{user-ext-eids-uids-id-empty.json => user-eids-source-empty.json} (63%) rename endpoints/openrtb2/sample-requests/invalid-whole/{user-ext-eids-source-empty.json => user-eids-uids-id-empty.json} (62%) rename endpoints/openrtb2/sample-requests/invalid-whole/{user-ext-eids-uids-missing.json => user-eids-uids-missing.json} (70%) delete mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-duplicate.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/device-sua.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.5-to-2.6-upconvert.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.6-to-2.5-downconvert.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/source-schain.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/targeting-optional-all-false.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/targeting-optional-includeformat-only.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/targeting-optional-includeformat.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/user-ext-eids-source-duplicate.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/user-ext-prebid-buyeruids.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-matching-bidder-name.json rename endpoints/openrtb2/sample-requests/valid-whole/supplementary/{imp-with-stored-bid-resp-insensitive-bidder-name.json => imp-with-stored-bid-resp-case-not-matching-bidder-name.json} (94%) create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-non-existing-bidder-name.json rename endpoints/openrtb2/sample-requests/valid-whole/supplementary/{req-ext-bidder-params-backward-compatible-merge.json => req-ext-bidder-params-promotion.json} (94%) create mode 100644 endpoints/openrtb2/sample-requests/video/video_invalid_sample_negative_tmax.json create mode 100644 errortypes/scope.go create mode 100644 errortypes/scope_test.go create mode 100644 exchange/exchangetest/alternate-bidder-codes.json delete mode 100644 exchange/exchangetest/bid-id-invalid.json delete mode 100644 exchange/exchangetest/bid-id-valid.json create mode 100644 exchange/exchangetest/dsa-default-ignored.json create mode 100644 exchange/exchangetest/dsa-default.json create mode 100644 exchange/exchangetest/dsa-not-required.json create mode 100644 exchange/exchangetest/dsa-required.json create mode 100644 exchange/exchangetest/firstpartydata-multibidder-user-eids.json create mode 100644 exchange/exchangetest/firstpartydata-user-eids-req-user-nil.json create mode 100644 exchange/exchangetest/firstpartydata-user-nileids-req-user-nil.json create mode 100644 exchange/exchangetest/floors_deal_enforcement.json create mode 100644 exchange/exchangetest/generate-bid-id-error.json create mode 100644 exchange/exchangetest/generate-bid-id-many.json create mode 100644 exchange/exchangetest/generate-bid-id-one.json create mode 100644 exchange/exchangetest/imp-fpd-invalid.json create mode 100644 exchange/exchangetest/imp-fpd-multiple-bidders.json create mode 100644 exchange/exchangetest/imp-fpd-multiple-imp.json create mode 100644 exchange/exchangetest/imp-fpd-skipped-validation.json create mode 100644 exchange/exchangetest/multi-bids-different-ortb-versions.json create mode 100644 exchange/exchangetest/targeting-always-include-deals.json create mode 100644 exchange/non_bid_reason_test.go delete mode 100644 firstpartydata/extmerger.go delete mode 100644 firstpartydata/extmerger_test.go create mode 100644 floors/fetcher.go create mode 100644 floors/fetcher_test.go create mode 100644 hooks/hookexecution/execution_test.go create mode 100644 injector/injector.go create mode 100644 injector/injector_test.go create mode 100644 modules/fiftyonedegrees/devicedetection/README.md create mode 100644 modules/fiftyonedegrees/devicedetection/account_info_extractor.go create mode 100644 modules/fiftyonedegrees/devicedetection/account_info_extractor_test.go create mode 100644 modules/fiftyonedegrees/devicedetection/account_validator.go create mode 100644 modules/fiftyonedegrees/devicedetection/account_validator_test.go create mode 100644 modules/fiftyonedegrees/devicedetection/config.go create mode 100644 modules/fiftyonedegrees/devicedetection/config_test.go create mode 100644 modules/fiftyonedegrees/devicedetection/context.go create mode 100644 modules/fiftyonedegrees/devicedetection/device_detector.go create mode 100644 modules/fiftyonedegrees/devicedetection/device_detector_test.go create mode 100644 modules/fiftyonedegrees/devicedetection/device_info_extractor.go create mode 100644 modules/fiftyonedegrees/devicedetection/device_info_extractor_test.go create mode 100644 modules/fiftyonedegrees/devicedetection/evidence_extractor.go create mode 100644 modules/fiftyonedegrees/devicedetection/evidence_extractor_test.go create mode 100644 modules/fiftyonedegrees/devicedetection/fiftyone_device_types.go create mode 100644 modules/fiftyonedegrees/devicedetection/fiftyone_device_types_test.go create mode 100644 modules/fiftyonedegrees/devicedetection/hook_auction_entrypoint.go create mode 100644 modules/fiftyonedegrees/devicedetection/hook_raw_auction_request.go create mode 100644 modules/fiftyonedegrees/devicedetection/models.go create mode 100644 modules/fiftyonedegrees/devicedetection/models_test.go create mode 100644 modules/fiftyonedegrees/devicedetection/module.go create mode 100644 modules/fiftyonedegrees/devicedetection/module_test.go create mode 100644 modules/fiftyonedegrees/devicedetection/request_headers_extractor.go create mode 100644 modules/fiftyonedegrees/devicedetection/request_headers_extractor_test.go create mode 100644 modules/fiftyonedegrees/devicedetection/sample/pbs.json create mode 100644 modules/fiftyonedegrees/devicedetection/sample/request_data.json create mode 100644 modules/fiftyonedegrees/devicedetection/sua_payload_extractor.go create mode 100644 openrtb_ext/imp_adelement.go create mode 100644 openrtb_ext/imp_admatic.go create mode 100644 openrtb_ext/imp_adtonos.go create mode 100644 openrtb_ext/imp_alkimi.go create mode 100644 openrtb_ext/imp_aso.go create mode 100644 openrtb_ext/imp_bidmatic.go create mode 100644 openrtb_ext/imp_bigoad.go rename openrtb_ext/{imp_bizzclick.go => imp_blasto.go} (51%) create mode 100644 openrtb_ext/imp_bwx.go create mode 100644 openrtb_ext/imp_cointraffic.go create mode 100644 openrtb_ext/imp_concert.go create mode 100644 openrtb_ext/imp_copper6ssp.go create mode 100644 openrtb_ext/imp_displayio.go create mode 100644 openrtb_ext/imp_driftpixel.go create mode 100644 openrtb_ext/imp_escalax.go create mode 100644 openrtb_ext/imp_loyal.go create mode 100644 openrtb_ext/imp_mediago.go create mode 100644 openrtb_ext/imp_melozen.go create mode 100644 openrtb_ext/imp_metax.go create mode 100644 openrtb_ext/imp_minutemedia.go create mode 100644 openrtb_ext/imp_missena.go create mode 100644 openrtb_ext/imp_oms.go create mode 100644 openrtb_ext/imp_oraki.go create mode 100644 openrtb_ext/imp_playdigo.go create mode 100644 openrtb_ext/imp_pubrise.go create mode 100644 openrtb_ext/imp_qt.go create mode 100644 openrtb_ext/imp_readpeak.go create mode 100644 openrtb_ext/imp_relevantdigital.go create mode 100644 openrtb_ext/imp_roulax.go create mode 100644 openrtb_ext/imp_smrtconnect.go create mode 100644 openrtb_ext/imp_sovrnXsp.go delete mode 100644 openrtb_ext/imp_suntContent.go create mode 100644 openrtb_ext/imp_theadx.go create mode 100644 openrtb_ext/imp_thetradedesk.go create mode 100644 openrtb_ext/imp_trustedstack.go create mode 100644 openrtb_ext/imp_vidazoo.go rename openrtb_ext/{imp_liftoff.go => imp_vungle.go} (85%) create mode 100644 openrtb_ext/imp_yandex.go create mode 100644 openrtb_ext/imp_zmaticoo.go create mode 100644 openrtb_ext/regs_test.go create mode 100644 ortb/request_validator.go create mode 100644 ortb/request_validator_audio.go create mode 100644 ortb/request_validator_audio_test.go create mode 100644 ortb/request_validator_banner.go create mode 100644 ortb/request_validator_banner_test.go create mode 100644 ortb/request_validator_native.go create mode 100644 ortb/request_validator_native_test.go create mode 100644 ortb/request_validator_pmp.go create mode 100644 ortb/request_validator_pmp_test.go create mode 100644 ortb/request_validator_test.go create mode 100644 ortb/request_validator_video.go create mode 100644 ortb/request_validator_video_test.go delete mode 100644 privacy/enforcement.go delete mode 100644 privacy/enforcement_test.go create mode 100644 privacysandbox/topics.go create mode 100644 privacysandbox/topics_test.go create mode 100644 router/bidder_params_tests/appnexus.json delete mode 100644 router/test_aliases.json create mode 100644 sample/001_banner/app.yaml create mode 100644 sample/001_banner/pbjs.html create mode 100644 sample/001_banner/stored_request.json create mode 100644 sample/001_banner/stored_response.json create mode 100644 sample/README.md create mode 100644 sample/docker-compose.yml create mode 100644 static/bidder-info/adelement.yaml create mode 100644 static/bidder-info/admatic.yaml create mode 100644 static/bidder-info/adtonos.yaml create mode 100644 static/bidder-info/alkimi.yaml create mode 100644 static/bidder-info/aso.yaml create mode 100644 static/bidder-info/bcmint.yaml create mode 100644 static/bidder-info/bidgency.yaml create mode 100644 static/bidder-info/bidmatic.yaml create mode 100644 static/bidder-info/bigoad.yaml create mode 100644 static/bidder-info/blasto.yaml create mode 100644 static/bidder-info/bwx.yaml create mode 100644 static/bidder-info/cointraffic.yaml create mode 100644 static/bidder-info/concert.yaml create mode 100644 static/bidder-info/copper6ssp.yaml create mode 100644 static/bidder-info/displayio.yaml create mode 100644 static/bidder-info/driftpixel.yaml create mode 100644 static/bidder-info/embimedia.yaml create mode 100644 static/bidder-info/escalax.yaml create mode 100644 static/bidder-info/felixads.yaml create mode 100644 static/bidder-info/filmzie.yaml create mode 100644 static/bidder-info/finative.yaml create mode 100644 static/bidder-info/indicue.yaml create mode 100644 static/bidder-info/jdpmedia.yaml create mode 100644 static/bidder-info/loyal.yaml create mode 100644 static/bidder-info/magnite.yaml create mode 100644 static/bidder-info/markapp.yaml create mode 100644 static/bidder-info/mediago.yaml create mode 100644 static/bidder-info/melozen.yaml create mode 100644 static/bidder-info/metax.yaml create mode 100644 static/bidder-info/minutemedia.yaml create mode 100644 static/bidder-info/missena.yaml create mode 100644 static/bidder-info/nativo.yaml create mode 100644 static/bidder-info/oms.yaml create mode 100644 static/bidder-info/oraki.yaml create mode 100644 static/bidder-info/playdigo.yaml create mode 100644 static/bidder-info/pubrise.yaml create mode 100644 static/bidder-info/qt.yaml create mode 100644 static/bidder-info/readpeak.yaml create mode 100644 static/bidder-info/relevantdigital.yaml rename static/bidder-info/{bizzclick.yaml => roulax.yaml} (54%) create mode 100644 static/bidder-info/smrtconnect.yaml create mode 100644 static/bidder-info/sovrnXsp.yaml create mode 100644 static/bidder-info/streamlyn.yaml create mode 100644 static/bidder-info/tgm.yaml create mode 100644 static/bidder-info/theadx.yaml create mode 100644 static/bidder-info/thetradedesk.yaml create mode 100644 static/bidder-info/tredio.yaml create mode 100644 static/bidder-info/trustedstack.yaml create mode 100644 static/bidder-info/vidazoo.yaml create mode 100644 static/bidder-info/vimayx.yaml rename static/bidder-info/{liftoff.yaml => vungle.yaml} (72%) create mode 100644 static/bidder-info/yandex.yaml create mode 100644 static/bidder-info/zmaticoo.yaml create mode 100644 static/bidder-params/adelement.json create mode 100644 static/bidder-params/admatic.json create mode 100644 static/bidder-params/adtonos.json create mode 100644 static/bidder-params/alkimi.json create mode 100644 static/bidder-params/aso.json create mode 100644 static/bidder-params/bidmatic.json create mode 100644 static/bidder-params/bigoad.json rename static/bidder-params/{bizzclick.json => blasto.json} (66%) create mode 100644 static/bidder-params/bwx.json create mode 100644 static/bidder-params/cointraffic.json create mode 100644 static/bidder-params/concert.json create mode 100644 static/bidder-params/copper6ssp.json create mode 100644 static/bidder-params/displayio.json create mode 100644 static/bidder-params/driftpixel.json delete mode 100644 static/bidder-params/epsilon.json create mode 100644 static/bidder-params/escalax.json delete mode 100644 static/bidder-params/freewheel-ssp.json create mode 100644 static/bidder-params/loyal.json create mode 100644 static/bidder-params/mediago.json create mode 100644 static/bidder-params/melozen.json create mode 100644 static/bidder-params/metax.json create mode 100644 static/bidder-params/minutemedia.json create mode 100644 static/bidder-params/missena.json create mode 100644 static/bidder-params/nativo.json create mode 100644 static/bidder-params/oms.json create mode 100644 static/bidder-params/oraki.json create mode 100644 static/bidder-params/playdigo.json create mode 100644 static/bidder-params/pubrise.json create mode 100644 static/bidder-params/qt.json create mode 100644 static/bidder-params/readpeak.json create mode 100644 static/bidder-params/relevantdigital.json create mode 100644 static/bidder-params/roulax.json create mode 100644 static/bidder-params/smrtconnect.json create mode 100644 static/bidder-params/sovrnXsp.json delete mode 100644 static/bidder-params/suntContent.json delete mode 100644 static/bidder-params/synacormedia.json create mode 100644 static/bidder-params/theadx.json create mode 100644 static/bidder-params/thetradedesk.json create mode 100644 static/bidder-params/trustedstack.json delete mode 100644 static/bidder-params/trustx.json create mode 100644 static/bidder-params/vidazoo.json rename static/bidder-params/{liftoff.json => vungle.json} (89%) delete mode 100644 static/bidder-params/yahooAdvertising.json delete mode 100644 static/bidder-params/yahoossp.json create mode 100644 static/bidder-params/yandex.json create mode 100644 static/bidder-params/zmaticoo.json create mode 100644 stored_requests/backends/file_fetcher/test/stored_responses/bar.json create mode 100644 stored_requests/backends/file_fetcher/test/stored_responses/escaped.json create mode 100644 util/jsonutil/merge.go create mode 100644 util/jsonutil/merge_test.go create mode 100644 util/ptrutil/ptrutil_test.go delete mode 100644 util/sliceutil/clone.go delete mode 100644 util/sliceutil/clone_test.go diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ebf6f614df3..f9bb82598ff 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "dockerfile": "Dockerfile", "args": { // Update the VARIANT arg to pick a version of Go - "VARIANT": "1.20", + "VARIANT": "1.22", // Options "INSTALL_NODE": "false", "NODE_VERSION": "lts/*" diff --git a/.github/workflows/adapter-code-coverage.yml b/.github/workflows/adapter-code-coverage.yml index b4c3f0745d6..37936a1d17d 100644 --- a/.github/workflows/adapter-code-coverage.yml +++ b/.github/workflows/adapter-code-coverage.yml @@ -1,29 +1,32 @@ -name: Adapter code coverage +name: Adapter Code Coverage + on: pull_request_target: paths: ["adapters/*/*.go"] + permissions: pull-requests: write contents: write + jobs: run-coverage: runs-on: ubuntu-latest steps: - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: - go-version: 1.20.5 + go-version: 1.22.3 - - name: Checkout pull request branch - uses: actions/checkout@v3 + - name: Checkout Code + uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} - - name: Get adapter directories + - name: Discover Adapter Directories id: get_directories - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: result-encoding: string script: | @@ -36,10 +39,11 @@ jobs: return "" } const helper = utils.diffHelper({github, context}) - const files = await helper.getDirectories(directoryExtractor) - return files.length == 0 ? "" : JSON.stringify(files); + const directories = await helper.getDirectories(directoryExtractor) + // run coverage for maximum of 2 directories + return (directories.length == 0 || directories.length > 2) ? "" : JSON.stringify(directories) - - name: Run coverage tests + - name: Run Coverage Tests id: run_coverage if: steps.get_directories.outputs.result != '' run: | @@ -66,14 +70,14 @@ jobs: cd .. rm -f -r ./* - - name: Checkout coverage-preview branch - uses: actions/checkout@v3 + - name: Checkout Coverage Preview Branch + uses: actions/checkout@v4 with: fetch-depth: 0 ref: coverage-preview repository: prebid/prebid-server - - name: Commit coverage files to coverage-preview branch + - name: Upload Coverage Results if: steps.run_coverage.outputs.coverage_dir != '' id: commit_coverage run: | @@ -87,13 +91,13 @@ jobs: git push origin coverage-preview echo "remote_coverage_preview_dir=${directory}" >> $GITHUB_OUTPUT - - name: Checkout master branch + - name: Checkout Master Branch if: steps.get_directories.outputs.result != '' run: git checkout master - - name: Add coverage summary to pull request + - name: Add Coverage Summary To Pull Request if: steps.run_coverage.outputs.coverage_dir != '' && steps.commit_coverage.outputs.remote_coverage_preview_dir != '' - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | const utils = require('./.github/workflows/helpers/pull-request-utils.js') diff --git a/.github/workflows/helpers/pull-request-utils.js b/.github/workflows/helpers/pull-request-utils.js index 941ae5b6953..73c80396473 100644 --- a/.github/workflows/helpers/pull-request-utils.js +++ b/.github/workflows/helpers/pull-request-utils.js @@ -1,7 +1,9 @@ const synchronizeEvent = "synchronize", openedEvent = "opened", completedStatus = "completed", - resultSize = 100 + resultSize = 100, + adminPermission = "admin", + writePermission = "write" class diffHelper { constructor(input) { @@ -407,8 +409,31 @@ class coverageHelper { } } +class userHelper { + constructor(input) { + this.owner = input.context.repo.owner + this.repo = input.context.repo.repo + this.github = input.github + this.user = input.user + } + + /* + Checks if the user has write permissions for the repository + @returns {boolean} - returns true if the user has write permissions, otherwise false + */ + async hasWritePermissions() { + const { data } = await this.github.rest.repos.getCollaboratorPermissionLevel({ + owner: this.owner, + repo: this.repo, + username: this.user, + }) + return data.permission === writePermission || data.permission === adminPermission + } +} + module.exports = { diffHelper: (input) => new diffHelper(input), semgrepHelper: (input) => new semgrepHelper(input), coverageHelper: (input) => new coverageHelper(input), + userHelper: (input) => new userHelper(input), } diff --git a/.github/workflows/issue_prioritization.yml b/.github/workflows/issue_prioritization.yml index 3843507b26e..ec58073d653 100644 --- a/.github/workflows/issue_prioritization.yml +++ b/.github/workflows/issue_prioritization.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Generate token id: generate_token - uses: tibdex/github-app-token@36464acb844fc53b9b8b2401da68844f6b05ebb0 + uses: tibdex/github-app-token@v2.1.0 with: app_id: ${{ secrets.PBS_PROJECT_APP_ID }} private_key: ${{ secrets.PBS_PROJECT_APP_PEM }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f1d2b10c41c..d3559732077 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,13 +25,25 @@ jobs: permissions: contents: read steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + repository: ${{ github.repository }} + ref: master - name: Check user permission - uses: actions-cool/check-user-permission@v2.2.0 + uses: actions/github-script@v7 id: check with: - require: 'write' + github-token: ${{ secrets.GITHUB_TOKEN }} + result-encoding: string + script: | + const utils = require('./.github/workflows/helpers/pull-request-utils.js') + const helper = utils.userHelper({github, context, user: '${{ github.actor }}'}) + const hasPermission = await helper.hasWritePermissions() + return hasPermission outputs: - hasWritePermission: ${{ steps.check.outputs.require-result }} + hasWritePermission: ${{ steps.check.outputs.result }} build-master: name: Build master @@ -40,7 +52,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@master + uses: actions/checkout@v4 with: fetch-depth: 0 repository: ${{ github.repository }} @@ -52,13 +64,12 @@ jobs: publish-tag: name: Publish tag needs: build-master - if: contains(needs.check-permission.outputs.hasWritePermission, 'true') permissions: contents: write runs-on: ubuntu-latest steps: - name: Checkout Prebid Server - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Create & publish tag @@ -111,7 +122,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Prebid Server - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Build image diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index baa10b93963..09a0fd56791 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Resolves to empty string for push events and falls back to HEAD. ref: ${{ github.event.pull_request.head.sha }} @@ -29,6 +29,6 @@ jobs: severity: 'CRITICAL,HIGH' - name: Upload Results To GitHub Security Tab - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: 'trivy-results.sarif' \ No newline at end of file diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 565bb9b871a..55fba5ee287 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -1,23 +1,26 @@ -name: Adapter semgrep checks +name: Adapter Semgrep Check + on: pull_request_target: paths: ["adapters/*/*.go"] + permissions: pull-requests: write + jobs: semgrep-check: runs-on: ubuntu-latest steps: - - name: Checkout repo - uses: actions/checkout@v3 + - name: Checkout Code + uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} - - name: Calculate diff + - name: Calculate Code Diff id: calculate_diff - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: result-encoding: string script: | @@ -29,7 +32,7 @@ jobs: const helper = utils.diffHelper({github, context, fileNameFilter, event: "${{github.event.action}}", testName: "${{github.job}}"}) return await helper.buildDiff() - - name: Should run semgrep + - name: Check For Changes id: should_run_semgrep run: | hasChanges=$(echo '${{ steps.calculate_diff.outputs.result }}' | jq .pullRequest.hasChanges) @@ -41,7 +44,7 @@ jobs: pip3 install semgrep==1.22.0 semgrep --version - - name: Run semgrep tests + - name: Run Semgrep id: run_semgrep_tests if: contains(steps.should_run_semgrep.outputs.hasChanges, 'true') run: | @@ -49,10 +52,10 @@ jobs: outputs=$(semgrep --gitlab-sast --config=.semgrep/adapter $unqouted_string | jq '[.vulnerabilities[] | {"file": .location.file, "severity": .severity, "start": .location.start_line, "end": .location.end_line, "message": (.message | gsub("\\n"; "\n"))}]' | jq -c | jq -R) echo "semgrep_result=${outputs}" >> "$GITHUB_OUTPUT" - - name: Add pull request comment + - name: Add Pull Request Comment id: add_pull_request_comment if: contains(steps.should_run_semgrep.outputs.hasChanges, 'true') - uses: actions/github-script@v6.4.1 + uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} result-encoding: string @@ -66,7 +69,7 @@ jobs: const { previousScan, currentScan } = await helper.addReviewComments() return previousScan.unAddressedComments + currentScan.newComments - - name: Adapter semgrep checks result + - name: Check Results if: contains(steps.should_run_semgrep.outputs.hasChanges, 'true') run: | if [ "${{steps.add_pull_request_comment.outputs.result}}" -ne "0" ]; then diff --git a/.github/workflows/validate-merge.yml b/.github/workflows/validate-merge.yml index 07f1bacaa45..d58a924d984 100644 --- a/.github/workflows/validate-merge.yml +++ b/.github/workflows/validate-merge.yml @@ -10,12 +10,12 @@ jobs: steps: - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: - go-version: 1.20.5 + go-version: 1.22.3 - name: Checkout Merged Branch - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Validate run: | diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 9047e1f468f..e40b165b8b3 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -10,18 +10,18 @@ jobs: validate: strategy: matrix: - go-version: [1.19.x, 1.20.x] + go-version: [1.21.x, 1.22.x] os: [ubuntu-20.04] runs-on: ${{ matrix.os }} steps: - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Resolves to empty string for push events and falls back to HEAD. ref: ${{ github.event.pull_request.head.sha }} diff --git a/.gitignore b/.gitignore index 4e298f2a3d9..8e22128eae6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ _obj _test .cover/ .idea/ +.vscode/ # Architecture specific extensions/prefixes *.[568vq] @@ -30,8 +31,8 @@ vendor # build artifacts prebid-server -build -debug +/build +/debug __debug_bin # config files @@ -42,11 +43,8 @@ inventory_url.yaml analytics/config/testFiles/ analytics/filesystem/testFiles/ -# autogenerated version file -# static/version.txt - -.idea/ -.vscode/ +# autogenerated files +prebid-server.iml # autogenerated mac file diff --git a/Dockerfile b/Dockerfile index 883cf395497..df5d0a9899f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,24 +3,28 @@ RUN apt-get update && \ apt-get -y upgrade && \ apt-get install -y wget WORKDIR /tmp -RUN wget https://dl.google.com/go/go1.20.5.linux-amd64.tar.gz && \ - tar -xf go1.20.5.linux-amd64.tar.gz && \ +RUN wget https://dl.google.com/go/go1.22.3.linux-amd64.tar.gz && \ + tar -xf go1.22.3.linux-amd64.tar.gz && \ mv go /usr/local RUN mkdir -p /app/prebid-server/ WORKDIR /app/prebid-server/ ENV GOROOT=/usr/local/go ENV PATH=$GOROOT/bin:$PATH ENV GOPROXY="https://proxy.golang.org" + +# Installing gcc as cgo uses it to build native code of some modules RUN apt-get update && \ - apt-get install -y git && \ + apt-get install -y git gcc && \ apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -ENV CGO_ENABLED 0 + +# CGO must be enabled because some modules depend on native C code +ENV CGO_ENABLED 1 COPY ./ ./ RUN go mod tidy RUN go mod vendor ARG TEST="false" RUN if [ "$TEST" != "false" ]; then ./validate.sh ; fi -RUN go build -mod=vendor -ldflags "-X github.com/prebid/prebid-server/version.Ver=`git describe --tags | sed 's/^v//'` -X github.com/prebid/prebid-server/version.Rev=`git rev-parse HEAD`" . +RUN go build -mod=vendor -ldflags "-X github.com/prebid/prebid-server/v3/version.Ver=`git describe --tags | sed 's/^v//'` -X github.com/prebid/prebid-server/v3/version.Rev=`git rev-parse HEAD`" . FROM ubuntu:20.04 AS release LABEL maintainer="hans.hjort@xandr.com" @@ -30,8 +34,10 @@ RUN chmod a+xr prebid-server COPY static static/ COPY stored_requests/data stored_requests/data RUN chmod -R a+r static/ stored_requests/data + +# Installing libatomic1 as it is a runtime dependency for some modules RUN apt-get update && \ - apt-get install -y ca-certificates mtr && \ + apt-get install -y ca-certificates mtr libatomic1 && \ apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* RUN adduser prebid_user USER prebid_user diff --git a/Makefile b/Makefile index b5b7281e5ed..8b68afe3bee 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ test: deps ifeq "$(adapter)" "" ./validate.sh else - go test github.com/prebid/prebid-server/adapters/$(adapter) -bench=. + go test github.com/prebid/prebid-server/v3/adapters/$(adapter) -bench=. endif # build-modules generates modules/builder.go file which provides a list of all available modules @@ -32,4 +32,8 @@ image: # format runs format format: - ./scripts/format.sh -f true \ No newline at end of file + ./scripts/format.sh -f true + +# formatcheck runs format for diagnostics, without modifying the code +formatcheck: + ./scripts/format.sh -f false diff --git a/README.md b/README.md index e2490378b86..83358e0f861 100644 --- a/README.md +++ b/README.md @@ -2,79 +2,114 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/prebid/prebid-server?style=flat-square)](https://goreportcard.com/report/github.com/prebid/prebid-server) ![Go Version](https://img.shields.io/github/go-mod/go-version/prebid/prebid-server?style=flat-square) -# Prebid Server +
+
+

Prebid Server Logo

+
-Prebid Server is an open source implementation of Server-Side Header Bidding. -It is managed by [Prebid.org](https://prebid.org/about/), -and upholds the principles from the [Prebid Code of Conduct](https://prebid.org/code-of-conduct/). +Prebid Server is an open-source solution for running real-time advertising auctions in the cloud. This project is part of the Prebid ecosystem, seamlessly integrating with Prebid.js and the Prebid Mobile SDKs to deliver world-class header bidding for any ad format and for any type of digital media. -This project does not support the same set of Bidders as Prebid.js, although there is overlap. -The current set can be found in the [adapters](./adapters) package. If you don't see the one you want, feel free to [contribute it](https://docs.prebid.org/prebid-server/developers/add-new-bidder-go.html). +## Getting Started +- [What is Prebid Server](https://docs.prebid.org/prebid-server/overview/prebid-server-overview.html) +- [Intro to Header Bidding](https://docs.prebid.org/overview/intro-to-header-bidding.html) +- [Header Bidding with Prebid](https://docs.prebid.org/overview/intro.html#header-bidding-with-prebid) +- [API Endpoints](https://docs.prebid.org/prebid-server/endpoints/pbs-endpoint-overview.html) + +## Configuring -For more information, see: +When hosting Prebid Server or developing locally, **you must set a default GDPR value**. This configuration determines whether GDPR is enabled when no regulatory signal is available in the request, where a value of `"0"` disables it by default and a value of `"1"` enables it. This is required as there is no consensus on a good default. -- [What is Prebid?](https://docs.prebid.org/overview/intro.html) -- [Prebid Server Overview](https://docs.prebid.org/prebid-server/overview/prebid-server-overview.html) -- [Current Bidders](https://docs.prebid.org/dev-docs/pbs-bidders.html) +Refer to the [configuration guide](docs/developers/configuration.md) for additional information and a list of available configuration options. -Please consider [registering your Prebid Server](https://docs.prebid.org/prebid-server/hosting/pbs-hosting.html#optional-registration) to get on the mailing list for updates, etc. +## Hosting Prebid Server +> [!NOTE] +> Please consider [registering as a Prebid Server host](https://docs.prebid.org/prebid-server/hosting/pbs-hosting.html#optional-registration) to join the mailing list for updates and feedback. -## Installation +The quickest way to host Prebid Server is to deploy our [official Docker image](https://hub.docker.com/r/prebid/prebid-server). If you're hosting the container with Kubernetes, you can configure Prebid Server with environment variables [using a pod file](https://kubernetes.io/docs/tasks/inject-data-application/define-interdependent-environment-variables/) or [using a config map](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#configure-all-key-value-pairs-in-a-configmap-as-container-environment-variables). Alternatively, you can use a configuration file [embedded in a config map](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#populate-a-volume-with-data-stored-in-a-configmap) which Prebid Server will read from at the path `/etc/config`. -First install [Go](https://golang.org/doc/install) version 1.19 or newer. +For deploying a fork, you can create a custom Docker container using the command: +``` bash +docker build --platform linux/amd64 -t prebid-server . +``` +or compile a standalone binary using the command: +``` bash +go build . +``` +**Note:** if building from source there are a couple dependencies to be aware of: +1. *Compile-time*. Some modules ship native code that requires `cgo` (comes with the `go` compiler) being enabled - by default it is and environment variable `CGO_ENABLED=1` do NOT set it to `0`. +2. *Compile-time*. `cgo` depends on the C-compiler, which usually is `gcc`, but can be changed by setting the value of `CC` env var, f.e. `CC=clang`. On ubuntu `gcc` can be installed via `sudo apt-get install gcc`. +3. *Runtime*. Some modules require `libatomic`. On ubuntu it is installed by running `sudo apt-get install libatomic1`. `libatomic1` is a dependency of `gcc`, so if you are building with `gcc` and running on the same machine, it is likely that `libatomic1` is already installed. -Note that prebid-server is using [Go modules](https://blog.golang.org/using-go-modules). -We officially support the most recent two major versions of the Go runtime. However, if you'd like to use a version <1.13 and are inside GOPATH `GO111MODULE` needs to be set to `GO111MODULE=on`. +Ensure that you deploy the `/static` directory, as Prebid Server requires those files at startup. -Download and prepare Prebid Server: +## Developing -```bash -cd YOUR_DIRECTORY -git clone https://github.com/prebid/prebid-server src/github.com/prebid/prebid-server -cd src/github.com/prebid/prebid-server +Prebid Server requires [Go](https://go.dev) version 1.21 or newer. You can develop on any operating system that Go supports; however, please note that our helper scripts are written in bash. + +1. Clone The Repository +``` bash +git clone git@github.com:prebid/prebid-server.git +cd prebid-server ``` -Run the automated tests: +3. Download Dependencies +``` bash +go mod download +``` +3. Verify Tests Pass ```bash ./validate.sh ``` -Or just run the server locally: - +4. Run The Server ```bash -go build . -./prebid-server +go run . ``` -Run format: -``` -make format -``` -or -```bash -./scripts/format.sh -f true -``` +By default, Prebid Server will attach to port 8000. To confirm the server is running, visit `http://localhost:8000/` in your web browser. -Load the landing page in your browser at `http://localhost:8000/`. -For the full API reference, see [the endpoint documentation](https://docs.prebid.org/prebid-server/endpoints/pbs-endpoint-overview.html) +### Code Style +To maintain consistency in the project's code, please: + +- Follow the recommendations set by [Effective Go](https://go.dev/doc/effective_go). This article provides a comprehensive guide on how to write idiomatic Go code, covering topics such as naming and formatting. Many IDEs will automatically format your code upon save. If you need to manaully format your code, either run the bash script or execute the make step: + ``` + ./scripts/format.sh -f true + ``` + ``` + make format + ``` -## Go Modules +- Prefer small functions with descriptive names instead of complex functions with comments. This approach helps make the code more readable, maintainable, and testable. -The packages within this repository are intended to be used as part of the Prebid Server compiled binary. If you -choose to import Prebid Server packages in other projects, please understand we make no promises on the stability -of exported types. +- Do not discard errors. You should implement appropriate error handling, such as gracefully falling back to a default behavior or bubbling up an error. + +### IDE Recommendation + +An option for developing Prebid Server in a reproducible environment isolated from your host OS is using Visual Studio Code with [Remote Container Setup](devcontainer.md). This is a recommendation, not a requirement. This approach is especially useful if you are developing on Windows, since the Remote Container runs within WSL providing you with the capability to execute bash scripts. + +## Importing Prebid Server + +Prebid Server is not currently intended to be imported by other projects. Go Modules is used to manage dependencies, which also makes it possible to import Prebid Server packages. This is not supported. We offer no guarantees regarding the stability of packages and do not adhere to semantic versioning guidelines. ## Contributing +> [!IMPORTANT] +> All contributions must follow the [Prebid Code of Conduct](https://prebid.org/code-of-conduct/) and the [Prebid Module Rules](https://docs.prebid.org/dev-docs/module-rules.html). -Want to [add an adapter](https://docs.prebid.org/prebid-server/developers/add-new-bidder-go.html)? Found a bug? Great! +### Bid Adapter +Bid Adapters transform OpenRTB requests and responses for communicating with a bidding server. This may be as simple as a passthrough or as complex as mapping to a custom data model. We invite you to contribute an adapter for your company. Consult our guide on [building a bid adapter](https://docs.prebid.org/prebid-server/developers/add-new-bidder-go.html) for more information. -Report bugs, request features, and suggest improvements [on Github](https://github.com/prebid/prebid-server/issues). +### Analytics Module +Analytics Modules enable business intelligence tools to collect data from Prebid Server to provide publishers and hosts with valuable insights into their header bidding traffic. We welcome you to contribute a module for your platform. Refer to our guide on [building an analytics module](https://docs.prebid.org/prebid-server/developers/pbs-build-an-analytics-adapter.html) for further information. -Or better yet, [open a pull request](https://github.com/prebid/prebid-server/compare) with the changes you'd like to see. +### Auction Module +Auction Modules allow hosts to extend the behavior of Prebid Server at specfic spots in the auction pipeline using existing modules or by developing custom functionality. Auction Modules may provide creative validation, traffic optimization, and real time data services among many other potential uses. We welcome vendors and community members to contribute modules that publishers and hosts may find useful. Consult our guide on [building an auction module](https://docs.prebid.org/prebid-server/developers/add-a-module.html) for more information. -## IDE Recommendations +### Feature +We welcome everyone to contribute to this project by implementing a specification or by proposing a new feature. Please review the [prioritized project board](https://github.com/orgs/prebid/projects/4), where you can select an issue labeled "Ready For Dev". To avoid redundant effort, kindly leave a comment on the issue stating your intention to take it on. To propose a feature, [open a new issue](https://github.com/prebid/prebid-server/issues/new/choose) with as much detail as possible for consideration by the Prebid Server Committee. +### Bug Fix +Bug reports may be submitted by [opening a new issue](https://github.com/prebid/prebid-server/issues/new/choose) and describing the error in detail with the steps to reproduce and example data. A member of the core development team will validate the bug and discuss next steps. You're encouraged to open an exploratory draft pull request to either demonstrate the bug by adding a test or offering a potential fix. The quickest way to start developing Prebid Server in a reproducible environment isolated from your host OS is by using Visual Studio Code with [Remote Container Setup](devcontainer.md). @@ -82,3 +117,6 @@ is by using Visual Studio Code with [Remote Container Setup](devcontainer.md). `docker build --platform linux/x86_64 -t infytv/infy:hb-2.0.0 .` `docker push infytv/infy:hb-2.0.0` +## Learning Materials + +To understand more about how Prebid Server in Go works and quickly spins up sample instances, refer to the `sample` folder which describes various structured and integrated examples. The examples are designed to run on any platform that supports `docker` container. diff --git a/account/account.go b/account/account.go index 1f5a3feee14..0087548cc79 100644 --- a/account/account.go +++ b/account/account.go @@ -6,20 +6,20 @@ import ( "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/util/iputil" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) // GetAccount looks up the config.Account object referenced by the given accountID, with access rules applied func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_requests.AccountFetcher, accountID string, me metrics.MetricsEngine) (account *config.Account, errs []error) { if cfg.AccountRequired && accountID == metrics.PublisherUnknown { return nil, []error{&errortypes.AcctRequired{ - Message: fmt.Sprintf("Prebid-server has been configured to discard requests without a valid Account ID. Please reach out to the prebid server host."), + Message: "Prebid-server has been configured to discard requests without a valid Account ID. Please reach out to the prebid server host.", }} } @@ -32,7 +32,7 @@ func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_r } if cfg.AccountRequired && cfg.AccountDefaults.Disabled { errs = append(errs, &errortypes.AcctRequired{ - Message: fmt.Sprintf("Prebid-server could not verify the Account ID. Please reach out to the prebid server host."), + Message: "Prebid-server could not verify the Account ID. Please reach out to the prebid server host.", }) return nil, errs } @@ -49,6 +49,11 @@ func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_r Message: fmt.Sprintf("The prebid-server account config for account id \"%s\" is malformed. Please reach out to the prebid server host.", accountID), }} } + if err := config.UnpackDSADefault(account.Privacy.DSA); err != nil { + return nil, []error{&errortypes.MalformedAcct{ + Message: fmt.Sprintf("The prebid-server account config DSA for account id \"%s\" is malformed. Please reach out to the prebid server host.", accountID), + }} + } // Fill in ID if needed, so it can be left out of account definition if len(account.ID) == 0 { @@ -111,7 +116,7 @@ func setDerivedConfig(account *config.Account) { if pc.VendorExceptions == nil { continue } - pc.VendorExceptionMap = make(map[openrtb_ext.BidderName]struct{}) + pc.VendorExceptionMap = make(map[string]struct{}) for _, v := range pc.VendorExceptions { pc.VendorExceptionMap[v] = struct{}{} } diff --git a/account/account_test.go b/account/account_test.go index 7a242f21188..c8b17b53ff8 100644 --- a/account/account_test.go +++ b/account/account_test.go @@ -6,18 +6,26 @@ import ( "fmt" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/iputil" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/util/iputil" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) +var ( + validDSA = `{\"dsarequired\":1,\"pubrender\":2,\"transparency\":[{\"domain\":\"test.com\"}]}` + invalidDSA = `{\"dsarequired\":\"invalid\",\"pubrender\":2,\"transparency\":[{\"domain\":\"test.com\"}]}` +) + var mockAccountData = map[string]json.RawMessage{ "valid_acct": json.RawMessage(`{"disabled":false}`), + "valid_acct_dsa": json.RawMessage(`{"disabled":false, "privacy": {"dsa": {"default": "` + validDSA + `"}}}`), + "invalid_acct_dsa": json.RawMessage(`{"disabled":false, "privacy": {"dsa": {"default": "` + invalidDSA + `"}}}`), "invalid_acct_ipv6_ipv4": json.RawMessage(`{"disabled":false, "privacy": {"ipv6": {"anon_keep_bits": -32}, "ipv4": {"anon_keep_bits": -16}}}`), "disabled_acct": json.RawMessage(`{"disabled":true}`), "malformed_acct": json.RawMessage(`{"disabled":"invalid type"}`), @@ -36,6 +44,16 @@ func (af mockAccountFetcher) FetchAccount(ctx context.Context, accountDefaultsJS } func TestGetAccount(t *testing.T) { + validDSA := &openrtb_ext.ExtRegsDSA{ + Required: ptrutil.ToPtr[int8](1), + PubRender: ptrutil.ToPtr[int8](2), + Transparency: []openrtb_ext.ExtBidDSATransparency{ + { + Domain: "test.com", + }, + }, + } + unknown := metrics.PublisherUnknown testCases := []struct { accountID string @@ -44,7 +62,8 @@ func TestGetAccount(t *testing.T) { // account_defaults.disabled disabled bool // checkDefaultIP indicates IPv6 and IPv6 should be set to default values - checkDefaultIP bool + wantDefaultIP bool + wantDSA *openrtb_ext.ExtRegsDSA // expected error, or nil if account should be found err error }{ @@ -66,7 +85,13 @@ func TestGetAccount(t *testing.T) { {accountID: "valid_acct", required: false, disabled: true, err: nil}, {accountID: "valid_acct", required: true, disabled: true, err: nil}, - {accountID: "invalid_acct_ipv6_ipv4", required: true, disabled: false, err: nil, checkDefaultIP: true}, + {accountID: "valid_acct_dsa", required: false, disabled: false, wantDSA: validDSA, err: nil}, + {accountID: "valid_acct_dsa", required: true, disabled: false, wantDSA: validDSA, err: nil}, + {accountID: "valid_acct_dsa", required: false, disabled: true, wantDSA: validDSA, err: nil}, + {accountID: "valid_acct_dsa", required: true, disabled: true, wantDSA: validDSA, err: nil}, + + {accountID: "invalid_acct_ipv6_ipv4", required: true, disabled: false, err: nil, wantDefaultIP: true}, + {accountID: "invalid_acct_dsa", required: false, disabled: false, err: &errortypes.MalformedAcct{}}, // pubID given and matches a host account explicitly disabled (Disabled: true on account json) {accountID: "disabled_acct", required: false, disabled: false, err: &errortypes.AccountDisabled{}}, @@ -111,10 +136,13 @@ func TestGetAccount(t *testing.T) { assert.Nil(t, account, "return account must be nil on error") assert.IsType(t, test.err, errors[0], "error is of unexpected type") } - if test.checkDefaultIP { + if test.wantDefaultIP { assert.Equal(t, account.Privacy.IPv6Config.AnonKeepBits, iputil.IPv6DefaultMaskingBitSize, "ipv6 should be set to default value") assert.Equal(t, account.Privacy.IPv4Config.AnonKeepBits, iputil.IPv4DefaultMaskingBitSize, "ipv4 should be set to default value") } + if test.wantDSA != nil { + assert.Equal(t, test.wantDSA, account.Privacy.DSA.DefaultUnpacked) + } }) } } @@ -122,7 +150,7 @@ func TestGetAccount(t *testing.T) { func TestSetDerivedConfig(t *testing.T) { tests := []struct { description string - purpose1VendorExceptions []openrtb_ext.BidderName + purpose1VendorExceptions []string feature1VendorExceptions []openrtb_ext.BidderName basicEnforcementVendors []string enforceAlgo string @@ -134,11 +162,11 @@ func TestSetDerivedConfig(t *testing.T) { }, { description: "One purpose 1 vendor exception", - purpose1VendorExceptions: []openrtb_ext.BidderName{"appnexus"}, + purpose1VendorExceptions: []string{"appnexus"}, }, { description: "Multiple purpose 1 vendor exceptions", - purpose1VendorExceptions: []openrtb_ext.BidderName{"appnexus", "rubicon"}, + purpose1VendorExceptions: []string{"appnexus", "rubicon"}, }, { description: "Nil feature 1 vendor exceptions", @@ -192,7 +220,7 @@ func TestSetDerivedConfig(t *testing.T) { setDerivedConfig(&account) - purpose1ExceptionMapKeys := make([]openrtb_ext.BidderName, 0) + purpose1ExceptionMapKeys := make([]string, 0) for k := range account.GDPR.Purpose1.VendorExceptionMap { purpose1ExceptionMapKeys = append(purpose1ExceptionMapKeys, k) } diff --git a/adapters/33across/33across.go b/adapters/33across/33across.go index 26349e8426b..94ddf1f91fd 100644 --- a/adapters/33across/33across.go +++ b/adapters/33across/33across.go @@ -5,12 +5,13 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/adcom1" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/adcom1" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type TtxAdapter struct { @@ -76,7 +77,7 @@ func (a *TtxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapter // Skip over imps whose extensions cannot be read since // we cannot glean Prod or ZoneID which are required to // group together. However let's not block request creation. - if err := json.Unmarshal(impCopy.Ext, &impExt); err == nil { + if err := jsonutil.Unmarshal(impCopy.Ext, &impExt); err == nil { impKey := impExt.Ttx.Prod + impExt.Ttx.Zoneid groupedImps[impKey] = append(groupedImps[impKey], impCopy) } else { @@ -114,6 +115,7 @@ func (a *TtxAdapter) makeRequest(request openrtb2.BidRequest, impList []openrtb2 Uri: a.endpoint, Body: reqJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }, nil } @@ -125,14 +127,14 @@ func makeImps(imp openrtb2.Imp) (openrtb2.Imp, error) { } var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { return openrtb2.Imp{}, &errortypes.BadInput{ Message: err.Error(), } } var ttxExt openrtb_ext.ExtImp33across - if err := json.Unmarshal(bidderExt.Bidder, &ttxExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &ttxExt); err != nil { return openrtb2.Imp{}, &errortypes.BadInput{ Message: err.Error(), } @@ -176,7 +178,7 @@ func makeReqExt(request *openrtb2.BidRequest) ([]byte, error) { var reqExt reqExt if len(request.Ext) > 0 { - if err := json.Unmarshal(request.Ext, &reqExt); err != nil { + if err := jsonutil.Unmarshal(request.Ext, &reqExt); err != nil { return nil, err } } @@ -214,7 +216,7 @@ func (a *TtxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequ var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -225,7 +227,7 @@ func (a *TtxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequ var bidExt bidExt var bidType openrtb_ext.BidType - if err := json.Unmarshal(sb.Bid[i].Ext, &bidExt); err != nil { + if err := jsonutil.Unmarshal(sb.Bid[i].Ext, &bidExt); err != nil { bidType = openrtb_ext.BidTypeBanner } else { bidType = getBidType(bidExt) @@ -243,8 +245,8 @@ func (a *TtxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequ func validateVideoParams(video *openrtb2.Video, prod string) (*openrtb2.Video, error) { videoCopy := *video - if videoCopy.W == 0 || - videoCopy.H == 0 || + if (videoCopy.W == nil || *videoCopy.W == 0) || + (videoCopy.H == nil || *videoCopy.H == 0) || videoCopy.Protocols == nil || videoCopy.MIMEs == nil || videoCopy.PlaybackMethod == nil { diff --git a/adapters/33across/33across_test.go b/adapters/33across/33across_test.go index bdc546a9627..3ed177deacd 100644 --- a/adapters/33across/33across_test.go +++ b/adapters/33across/33across_test.go @@ -3,9 +3,9 @@ package ttx import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/33across/33acrosstest/exemplary/bidresponse-defaults.json b/adapters/33across/33acrosstest/exemplary/bidresponse-defaults.json index 50fba69bde1..9f6a28e6d19 100644 --- a/adapters/33across/33acrosstest/exemplary/bidresponse-defaults.json +++ b/adapters/33across/33acrosstest/exemplary/bidresponse-defaults.json @@ -61,7 +61,8 @@ } ], "site": {} - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/33across/33acrosstest/exemplary/instream-video-defaults.json b/adapters/33across/33acrosstest/exemplary/instream-video-defaults.json index c99e535cdb7..9b7c717153f 100644 --- a/adapters/33across/33acrosstest/exemplary/instream-video-defaults.json +++ b/adapters/33across/33acrosstest/exemplary/instream-video-defaults.json @@ -59,7 +59,8 @@ } ], "site": {} - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/33across/33acrosstest/exemplary/multi-format.json b/adapters/33across/33acrosstest/exemplary/multi-format.json index 3315ff72559..af09bb75cb6 100644 --- a/adapters/33across/33acrosstest/exemplary/multi-format.json +++ b/adapters/33across/33acrosstest/exemplary/multi-format.json @@ -64,7 +64,8 @@ } ], "site": {} - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/33across/33acrosstest/exemplary/multi-imp-banner.json b/adapters/33across/33acrosstest/exemplary/multi-imp-banner.json index db03a001526..c0b6166548b 100644 --- a/adapters/33across/33acrosstest/exemplary/multi-imp-banner.json +++ b/adapters/33across/33acrosstest/exemplary/multi-imp-banner.json @@ -97,7 +97,8 @@ } ], "site": {} - } + }, + "impIDs":["test-imp-id1","test-imp-id2"] }, "mockResponse": { "status": 200, @@ -172,7 +173,8 @@ } ], "site": {} - } + }, + "impIDs":["test-imp-id3"] }, "mockResponse": { "status": 200, @@ -233,7 +235,8 @@ } ], "site": {} - } + }, + "impIDs":["test-imp-id4"] }, "mockResponse": { "status": 200, diff --git a/adapters/33across/33acrosstest/exemplary/optional-params.json b/adapters/33across/33acrosstest/exemplary/optional-params.json index f429f9458a7..f6171b3f242 100644 --- a/adapters/33across/33acrosstest/exemplary/optional-params.json +++ b/adapters/33across/33acrosstest/exemplary/optional-params.json @@ -50,7 +50,8 @@ } ], "site": {} - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/33across/33acrosstest/exemplary/outstream-video-defaults.json b/adapters/33across/33acrosstest/exemplary/outstream-video-defaults.json index fdd422c7a63..7044a145a7c 100644 --- a/adapters/33across/33acrosstest/exemplary/outstream-video-defaults.json +++ b/adapters/33across/33acrosstest/exemplary/outstream-video-defaults.json @@ -58,7 +58,8 @@ } ], "site": {} - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/33across/33acrosstest/exemplary/site-banner.json b/adapters/33across/33acrosstest/exemplary/site-banner.json index 552daf25224..e55a92ed887 100644 --- a/adapters/33across/33acrosstest/exemplary/site-banner.json +++ b/adapters/33across/33acrosstest/exemplary/site-banner.json @@ -49,7 +49,8 @@ } ], "site": {} - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/33across/33acrosstest/exemplary/site-video.json b/adapters/33across/33acrosstest/exemplary/site-video.json index 3cf44775efb..12170cb77d2 100644 --- a/adapters/33across/33acrosstest/exemplary/site-video.json +++ b/adapters/33across/33acrosstest/exemplary/site-video.json @@ -61,7 +61,8 @@ } ], "site": {} - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/33across/33acrosstest/supplemental/multi-imp-mixed-validation.json b/adapters/33across/33acrosstest/supplemental/multi-imp-mixed-validation.json index a2bc7890e96..3aaa6e94e90 100644 --- a/adapters/33across/33acrosstest/supplemental/multi-imp-mixed-validation.json +++ b/adapters/33across/33acrosstest/supplemental/multi-imp-mixed-validation.json @@ -58,7 +58,8 @@ } ], "site": {} - } + }, + "impIDs":["test-imp-id1"] }, "mockResponse": { "status": 200, diff --git a/adapters/33across/33acrosstest/supplemental/status-not-ok.json b/adapters/33across/33acrosstest/supplemental/status-not-ok.json index 2c7fcd013f3..668e8a0bbae 100644 --- a/adapters/33across/33acrosstest/supplemental/status-not-ok.json +++ b/adapters/33across/33acrosstest/supplemental/status-not-ok.json @@ -49,7 +49,8 @@ } ], "site": {} - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/33across/33acrosstest/supplemental/video-validation-fail-size-null.json b/adapters/33across/33acrosstest/supplemental/video-validation-fail-size-null.json new file mode 100644 index 00000000000..c9389fb86c2 --- /dev/null +++ b/adapters/33across/33acrosstest/supplemental/video-validation-fail-size-null.json @@ -0,0 +1,29 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "protocols": [2], + "playbackmethod": [2], + "mimes": ["foo", "bar"] + }, + "ext": { + "bidder": { + "siteId": "fake-site-id", + "productId": "siab" + } + } + } + ], + "site": {} + }, + + "expectedMakeRequestsErrors": [ + { + "value": "One or more invalid or missing video field(s) w, h, protocols, mimes, playbackmethod", + "comparison": "literal" + } + ] +} diff --git a/adapters/33across/33acrosstest/supplemental/video-validation-fail-size-partial.json b/adapters/33across/33acrosstest/supplemental/video-validation-fail-size-partial.json new file mode 100644 index 00000000000..230eaac05df --- /dev/null +++ b/adapters/33across/33acrosstest/supplemental/video-validation-fail-size-partial.json @@ -0,0 +1,30 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "h": 100, + "protocols": [2], + "playbackmethod": [2], + "mimes": ["foo", "bar"] + }, + "ext": { + "bidder": { + "siteId": "fake-site-id", + "productId": "siab" + } + } + } + ], + "site": {} + }, + + "expectedMakeRequestsErrors": [ + { + "value": "One or more invalid or missing video field(s) w, h, protocols, mimes, playbackmethod", + "comparison": "literal" + } + ] +} diff --git a/adapters/33across/33acrosstest/supplemental/video-validation-fail-size-zero.json b/adapters/33across/33acrosstest/supplemental/video-validation-fail-size-zero.json new file mode 100644 index 00000000000..32927e2b455 --- /dev/null +++ b/adapters/33across/33acrosstest/supplemental/video-validation-fail-size-zero.json @@ -0,0 +1,31 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 0, + "h": 0, + "protocols": [2], + "playbackmethod": [2], + "mimes": ["foo", "bar"] + }, + "ext": { + "bidder": { + "siteId": "fake-site-id", + "productId": "siab" + } + } + } + ], + "site": {} + }, + + "expectedMakeRequestsErrors": [ + { + "value": "One or more invalid or missing video field(s) w, h, protocols, mimes, playbackmethod", + "comparison": "literal" + } + ] +} diff --git a/adapters/33across/params_test.go b/adapters/33across/params_test.go index 2d488c4148c..3169a1d9944 100644 --- a/adapters/33across/params_test.go +++ b/adapters/33across/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // This file actually intends to test static/bidder-params/33across.json diff --git a/adapters/aax/aax.go b/adapters/aax/aax.go index 86994c6dea2..10969559658 100644 --- a/adapters/aax/aax.go +++ b/adapters/aax/aax.go @@ -6,11 +6,12 @@ import ( "net/http" "net/url" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { @@ -38,6 +39,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E Uri: a.endpoint, Body: reqJson, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }}, errs } @@ -62,7 +64,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -95,7 +97,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co func getMediaTypeForImp(bid openrtb2.Bid, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { var bidExt aaxResponseBidExt - err := json.Unmarshal(bid.Ext, &bidExt) + err := jsonutil.Unmarshal(bid.Ext, &bidExt) if err == nil { switch bidExt.AdCodeType { case "banner": diff --git a/adapters/aax/aax_test.go b/adapters/aax/aax_test.go index 6a5eaed5dfe..ad24a2d62e1 100644 --- a/adapters/aax/aax_test.go +++ b/adapters/aax/aax_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/aax/aaxtest/exemplary/multi-format.json b/adapters/aax/aaxtest/exemplary/multi-format.json index 493f268ccf5..dd757882b7a 100644 --- a/adapters/aax/aaxtest/exemplary/multi-format.json +++ b/adapters/aax/aaxtest/exemplary/multi-format.json @@ -68,7 +68,8 @@ } } ] - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 204, diff --git a/adapters/aax/aaxtest/exemplary/multi-imps.json b/adapters/aax/aaxtest/exemplary/multi-imps.json index 9ad02293e95..25b39323456 100644 --- a/adapters/aax/aaxtest/exemplary/multi-imps.json +++ b/adapters/aax/aaxtest/exemplary/multi-imps.json @@ -80,7 +80,8 @@ } } ] - } + }, + "impIDs":["1","2"] }, "mockResponse": { "status": 200, diff --git a/adapters/aax/aaxtest/exemplary/no-bid.json b/adapters/aax/aaxtest/exemplary/no-bid.json index 2ffbdac7e93..e2a6ac2a1d7 100644 --- a/adapters/aax/aaxtest/exemplary/no-bid.json +++ b/adapters/aax/aaxtest/exemplary/no-bid.json @@ -46,7 +46,8 @@ } } ] - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 204, diff --git a/adapters/aax/aaxtest/exemplary/optional-params.json b/adapters/aax/aaxtest/exemplary/optional-params.json index 37458ed6d0e..b8c2a264c89 100644 --- a/adapters/aax/aaxtest/exemplary/optional-params.json +++ b/adapters/aax/aaxtest/exemplary/optional-params.json @@ -48,7 +48,8 @@ } } ] - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 204, diff --git a/adapters/aax/aaxtest/exemplary/simple-banner.json b/adapters/aax/aaxtest/exemplary/simple-banner.json index 7e8c0fce1d8..f7eb36b3fc7 100644 --- a/adapters/aax/aaxtest/exemplary/simple-banner.json +++ b/adapters/aax/aaxtest/exemplary/simple-banner.json @@ -46,7 +46,8 @@ } } ] - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 200, diff --git a/adapters/aax/aaxtest/exemplary/simple-video.json b/adapters/aax/aaxtest/exemplary/simple-video.json index 6ee8d3d8bfe..7e11ee52126 100644 --- a/adapters/aax/aaxtest/exemplary/simple-video.json +++ b/adapters/aax/aaxtest/exemplary/simple-video.json @@ -52,7 +52,8 @@ } } ] - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 200, diff --git a/adapters/aax/aaxtest/supplemental/invalid-req-400-status-code-bad-request.json b/adapters/aax/aaxtest/supplemental/invalid-req-400-status-code-bad-request.json index 276e0fc381a..0091c1aa2ab 100644 --- a/adapters/aax/aaxtest/supplemental/invalid-req-400-status-code-bad-request.json +++ b/adapters/aax/aaxtest/supplemental/invalid-req-400-status-code-bad-request.json @@ -80,7 +80,8 @@ "buyeruid": "0000-000-000-0000" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 400 diff --git a/adapters/aax/aaxtest/supplemental/invalid-resp-diff-imp-id.json b/adapters/aax/aaxtest/supplemental/invalid-resp-diff-imp-id.json index 2d9d02d66ba..28894b288ad 100644 --- a/adapters/aax/aaxtest/supplemental/invalid-resp-diff-imp-id.json +++ b/adapters/aax/aaxtest/supplemental/invalid-resp-diff-imp-id.json @@ -86,7 +86,8 @@ "gdpr": 0 } } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/aax/aaxtest/supplemental/invalid-resp-multi-imp-type.json b/adapters/aax/aaxtest/supplemental/invalid-resp-multi-imp-type.json index b0a0b750ab1..087e5ff63ae 100644 --- a/adapters/aax/aaxtest/supplemental/invalid-resp-multi-imp-type.json +++ b/adapters/aax/aaxtest/supplemental/invalid-resp-multi-imp-type.json @@ -112,7 +112,8 @@ "gdpr": 0 } } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/aax/aaxtest/supplemental/valid-req-200-bid-response-from-aax.json b/adapters/aax/aaxtest/supplemental/valid-req-200-bid-response-from-aax.json index bad8743488c..72243d19696 100644 --- a/adapters/aax/aaxtest/supplemental/valid-req-200-bid-response-from-aax.json +++ b/adapters/aax/aaxtest/supplemental/valid-req-200-bid-response-from-aax.json @@ -86,7 +86,8 @@ "gdpr": 0 } } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/aax/aaxtest/supplemental/valid-req-204-response-from-aax.json b/adapters/aax/aaxtest/supplemental/valid-req-204-response-from-aax.json index db8219029ff..3076cc842d2 100644 --- a/adapters/aax/aaxtest/supplemental/valid-req-204-response-from-aax.json +++ b/adapters/aax/aaxtest/supplemental/valid-req-204-response-from-aax.json @@ -54,7 +54,8 @@ "id": "imp-id" } ] - } + }, + "impIDs":["imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/aax/params_test.go b/adapters/aax/params_test.go index edf9fb6fc48..0f93a063e65 100644 --- a/adapters/aax/params_test.go +++ b/adapters/aax/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // This file actually intends to test static/bidder-params/aax.json diff --git a/adapters/aceex/aceex.go b/adapters/aceex/aceex.go index 61863f0b8a8..73a040bbe56 100644 --- a/adapters/aceex/aceex.go +++ b/adapters/aceex/aceex.go @@ -6,12 +6,13 @@ import ( "net/http" "text/template" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { @@ -84,18 +85,19 @@ func (a *adapter) MakeRequests( Body: reqJSON, Uri: url, Headers: getHeaders(openRTBRequest), + ImpIDs: openrtb_ext.GetImpIDs(openRTBRequest.Imp), }}, nil } func (a *adapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtAceex, error) { var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ Message: "ext.bidder not provided", } } var aceexExt openrtb_ext.ExtAceex - if err := json.Unmarshal(bidderExt.Bidder, &aceexExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &aceexExt); err != nil { return nil, &errortypes.BadInput{ Message: "ext.bidder not provided", } @@ -150,7 +152,7 @@ func (a *adapter) MakeBids( responseBody := bidderRawResponse.Body var bidResp openrtb2.BidResponse - if err := json.Unmarshal(responseBody, &bidResp); err != nil { + if err := jsonutil.Unmarshal(responseBody, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: "Bad Server Response", }} diff --git a/adapters/aceex/aceex_test.go b/adapters/aceex/aceex_test.go index ec0e0fec710..7aef7dd93f7 100644 --- a/adapters/aceex/aceex_test.go +++ b/adapters/aceex/aceex_test.go @@ -3,9 +3,9 @@ package aceex import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/aceex/aceextest/exemplary/banner-app.json b/adapters/aceex/aceextest/exemplary/banner-app.json index f6509699ad0..f83826b0188 100644 --- a/adapters/aceex/aceextest/exemplary/banner-app.json +++ b/adapters/aceex/aceextest/exemplary/banner-app.json @@ -94,7 +94,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/aceex/aceextest/exemplary/banner-web.json b/adapters/aceex/aceextest/exemplary/banner-web.json index cf0d69bed7f..8b90d477666 100644 --- a/adapters/aceex/aceextest/exemplary/banner-web.json +++ b/adapters/aceex/aceextest/exemplary/banner-web.json @@ -82,7 +82,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/aceex/aceextest/exemplary/native-app.json b/adapters/aceex/aceextest/exemplary/native-app.json index 3e8d0fe2b96..b5daeb94bcb 100644 --- a/adapters/aceex/aceextest/exemplary/native-app.json +++ b/adapters/aceex/aceextest/exemplary/native-app.json @@ -94,7 +94,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/aceex/aceextest/exemplary/native-web.json b/adapters/aceex/aceextest/exemplary/native-web.json index a0c220ef74c..5874d8591a5 100644 --- a/adapters/aceex/aceextest/exemplary/native-web.json +++ b/adapters/aceex/aceextest/exemplary/native-web.json @@ -83,7 +83,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/aceex/aceextest/exemplary/video-app.json b/adapters/aceex/aceextest/exemplary/video-app.json index cc27ad608f3..95bff875682 100644 --- a/adapters/aceex/aceextest/exemplary/video-app.json +++ b/adapters/aceex/aceextest/exemplary/video-app.json @@ -104,7 +104,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/aceex/aceextest/exemplary/video-web.json b/adapters/aceex/aceextest/exemplary/video-web.json index a15590860c9..eac6c195d58 100644 --- a/adapters/aceex/aceextest/exemplary/video-web.json +++ b/adapters/aceex/aceextest/exemplary/video-web.json @@ -92,7 +92,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/aceex/aceextest/supplemental/empty-seatbid-array.json b/adapters/aceex/aceextest/supplemental/empty-seatbid-array.json index e53e6252135..dc0ac9be496 100644 --- a/adapters/aceex/aceextest/supplemental/empty-seatbid-array.json +++ b/adapters/aceex/aceextest/supplemental/empty-seatbid-array.json @@ -104,7 +104,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/aceex/aceextest/supplemental/invalid-response.json b/adapters/aceex/aceextest/supplemental/invalid-response.json index c1d62068089..ec96f10a2ce 100644 --- a/adapters/aceex/aceextest/supplemental/invalid-response.json +++ b/adapters/aceex/aceextest/supplemental/invalid-response.json @@ -95,7 +95,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/aceex/aceextest/supplemental/status-code-bad-request.json b/adapters/aceex/aceextest/supplemental/status-code-bad-request.json index 553a9264a10..4af8bb55792 100644 --- a/adapters/aceex/aceextest/supplemental/status-code-bad-request.json +++ b/adapters/aceex/aceextest/supplemental/status-code-bad-request.json @@ -76,7 +76,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 400 diff --git a/adapters/aceex/aceextest/supplemental/status-code-no-content.json b/adapters/aceex/aceextest/supplemental/status-code-no-content.json index c669c3ce450..c907f8615b6 100644 --- a/adapters/aceex/aceextest/supplemental/status-code-no-content.json +++ b/adapters/aceex/aceextest/supplemental/status-code-no-content.json @@ -57,7 +57,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/aceex/aceextest/supplemental/status-code-other-error.json b/adapters/aceex/aceextest/supplemental/status-code-other-error.json index 18892fe5e71..0c05ae9bc9f 100644 --- a/adapters/aceex/aceextest/supplemental/status-code-other-error.json +++ b/adapters/aceex/aceextest/supplemental/status-code-other-error.json @@ -62,7 +62,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 306 diff --git a/adapters/aceex/aceextest/supplemental/status-code-service-unavailable.json b/adapters/aceex/aceextest/supplemental/status-code-service-unavailable.json index 9c244ce36d5..4f3071b610d 100644 --- a/adapters/aceex/aceextest/supplemental/status-code-service-unavailable.json +++ b/adapters/aceex/aceextest/supplemental/status-code-service-unavailable.json @@ -62,7 +62,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 503 diff --git a/adapters/aceex/params_test.go b/adapters/aceex/params_test.go index 220adb23379..c09e2683073 100644 --- a/adapters/aceex/params_test.go +++ b/adapters/aceex/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/acuityads/acuityads.go b/adapters/acuityads/acuityads.go index 4370beb72d1..e05ded95e80 100644 --- a/adapters/acuityads/acuityads.go +++ b/adapters/acuityads/acuityads.go @@ -6,12 +6,13 @@ import ( "net/http" "text/template" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type AcuityAdsAdapter struct { @@ -84,18 +85,19 @@ func (a *AcuityAdsAdapter) MakeRequests( Body: reqJSON, Uri: url, Headers: getHeaders(openRTBRequest), + ImpIDs: openrtb_ext.GetImpIDs(openRTBRequest.Imp), }}, nil } func (a *AcuityAdsAdapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtAcuityAds, error) { var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ Message: "ext.bidder not provided", } } var acuityAdsExt openrtb_ext.ExtAcuityAds - if err := json.Unmarshal(bidderExt.Bidder, &acuityAdsExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &acuityAdsExt); err != nil { return nil, &errortypes.BadInput{ Message: "ext.bidder not provided", } @@ -154,7 +156,7 @@ func (a *AcuityAdsAdapter) MakeBids( responseBody := bidderRawResponse.Body var bidResp openrtb2.BidResponse - if err := json.Unmarshal(responseBody, &bidResp); err != nil { + if err := jsonutil.Unmarshal(responseBody, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: "Bad Server Response", }} diff --git a/adapters/acuityads/acuityads_test.go b/adapters/acuityads/acuityads_test.go index ea9d4f24352..b19abd3f7d9 100644 --- a/adapters/acuityads/acuityads_test.go +++ b/adapters/acuityads/acuityads_test.go @@ -3,9 +3,9 @@ package acuityads import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/acuityads/acuityadstest/exemplary/banner-app.json b/adapters/acuityads/acuityadstest/exemplary/banner-app.json index 526f1ea69ac..2cf1e4849e8 100644 --- a/adapters/acuityads/acuityadstest/exemplary/banner-app.json +++ b/adapters/acuityads/acuityadstest/exemplary/banner-app.json @@ -95,7 +95,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/acuityads/acuityadstest/exemplary/banner-web.json b/adapters/acuityads/acuityadstest/exemplary/banner-web.json index f4616bc4030..9968ce3fe6b 100644 --- a/adapters/acuityads/acuityadstest/exemplary/banner-web.json +++ b/adapters/acuityads/acuityadstest/exemplary/banner-web.json @@ -83,7 +83,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/acuityads/acuityadstest/exemplary/native-app.json b/adapters/acuityads/acuityadstest/exemplary/native-app.json index f10a0a9af4d..6022d6487f8 100644 --- a/adapters/acuityads/acuityadstest/exemplary/native-app.json +++ b/adapters/acuityads/acuityadstest/exemplary/native-app.json @@ -95,7 +95,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/acuityads/acuityadstest/exemplary/native-web.json b/adapters/acuityads/acuityadstest/exemplary/native-web.json index 96b3ca06aec..a9333bc667f 100644 --- a/adapters/acuityads/acuityadstest/exemplary/native-web.json +++ b/adapters/acuityads/acuityadstest/exemplary/native-web.json @@ -83,7 +83,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/acuityads/acuityadstest/exemplary/video-app.json b/adapters/acuityads/acuityadstest/exemplary/video-app.json index 4130602db47..4e51426b670 100644 --- a/adapters/acuityads/acuityadstest/exemplary/video-app.json +++ b/adapters/acuityads/acuityadstest/exemplary/video-app.json @@ -105,7 +105,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/acuityads/acuityadstest/exemplary/video-web.json b/adapters/acuityads/acuityadstest/exemplary/video-web.json index 2efeffec320..fad814fdee9 100644 --- a/adapters/acuityads/acuityadstest/exemplary/video-web.json +++ b/adapters/acuityads/acuityadstest/exemplary/video-web.json @@ -93,7 +93,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/acuityads/acuityadstest/supplemental/empty-seatbid-array.json b/adapters/acuityads/acuityadstest/supplemental/empty-seatbid-array.json index b822421ad4f..d1af6aac4b1 100644 --- a/adapters/acuityads/acuityadstest/supplemental/empty-seatbid-array.json +++ b/adapters/acuityads/acuityadstest/supplemental/empty-seatbid-array.json @@ -105,7 +105,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/acuityads/acuityadstest/supplemental/invalid-response.json b/adapters/acuityads/acuityadstest/supplemental/invalid-response.json index 16ba7ada294..129dca5112e 100644 --- a/adapters/acuityads/acuityadstest/supplemental/invalid-response.json +++ b/adapters/acuityads/acuityadstest/supplemental/invalid-response.json @@ -96,7 +96,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/acuityads/acuityadstest/supplemental/status-code-bad-request.json b/adapters/acuityads/acuityadstest/supplemental/status-code-bad-request.json index 87b72b07f68..391df714b9e 100644 --- a/adapters/acuityads/acuityadstest/supplemental/status-code-bad-request.json +++ b/adapters/acuityads/acuityadstest/supplemental/status-code-bad-request.json @@ -77,7 +77,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 400 diff --git a/adapters/acuityads/acuityadstest/supplemental/status-code-no-content.json b/adapters/acuityads/acuityadstest/supplemental/status-code-no-content.json index 130710db361..0d4fe2b20e8 100644 --- a/adapters/acuityads/acuityadstest/supplemental/status-code-no-content.json +++ b/adapters/acuityads/acuityadstest/supplemental/status-code-no-content.json @@ -58,7 +58,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/acuityads/acuityadstest/supplemental/status-code-other-error.json b/adapters/acuityads/acuityadstest/supplemental/status-code-other-error.json index 52042483b2c..78c2d39f3a4 100644 --- a/adapters/acuityads/acuityadstest/supplemental/status-code-other-error.json +++ b/adapters/acuityads/acuityadstest/supplemental/status-code-other-error.json @@ -63,7 +63,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 306 diff --git a/adapters/acuityads/acuityadstest/supplemental/status-code-service-unavailable.json b/adapters/acuityads/acuityadstest/supplemental/status-code-service-unavailable.json index 634b07cab33..45faf7a12fe 100644 --- a/adapters/acuityads/acuityadstest/supplemental/status-code-service-unavailable.json +++ b/adapters/acuityads/acuityadstest/supplemental/status-code-service-unavailable.json @@ -63,7 +63,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 503 diff --git a/adapters/acuityads/params_test.go b/adapters/acuityads/params_test.go index 892fe9a646d..756dda3ea1e 100644 --- a/adapters/acuityads/params_test.go +++ b/adapters/acuityads/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/adapterstest/adapter_test_util.go b/adapters/adapterstest/adapter_test_util.go index 46527356fe2..d58437b8fd8 100644 --- a/adapters/adapterstest/adapter_test_util.go +++ b/adapters/adapterstest/adapter_test_util.go @@ -8,7 +8,7 @@ import ( "net/http" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" ) // OrtbMockService Represents a scaffolded OpenRTB service. diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go index 39b1e945f7d..c2124abfcad 100644 --- a/adapters/adapterstest/test_json.go +++ b/adapters/adapterstest/test_json.go @@ -8,13 +8,16 @@ import ( "os" "path/filepath" "regexp" + "strings" "testing" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/mitchellh/copystructure" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" "github.com/yudai/gojsondiff" "github.com/yudai/gojsondiff/formatter" @@ -200,6 +203,7 @@ type httpRequest struct { Body json.RawMessage `json:"body"` Uri string `json:"uri"` Headers http.Header `json:"headers"` + ImpIDs []string `json:"impIDs"` } type httpResponse struct { @@ -223,9 +227,10 @@ type expectedBidResponse struct { } type expectedBid struct { - Bid json.RawMessage `json:"bid"` - Type string `json:"type"` - Seat string `json:"seat"` + Bid json.RawMessage `json:"bid"` + Type string `json:"type"` + Seat string `json:"seat"` + Video json.RawMessage `json:"video,omitempty"` } // --------------------------------------- @@ -269,6 +274,10 @@ func assertErrorList(t *testing.T, description string, actual []error, expected if matched, _ := regexp.MatchString(expected[i].Value, actual[i].Error()); !matched { t.Errorf(`%s error[%d] had wrong message. Expected match with regex "%s", got "%s"`, description, i, expected[i].Value, actual[i].Error()) } + } else if expected[i].Comparison == "startswith" { + if !strings.HasPrefix(actual[i].Error(), expected[i].Value) { + t.Errorf(`%s error[%d] had wrong message. Expected to start with "%s", got "%s"`, description, i, expected[i].Value, actual[i].Error()) + } } else { t.Fatalf(`invalid comparison type "%s"`, expected[i].Comparison) } @@ -318,6 +327,15 @@ func diffHttpRequests(description string, actual *adapters.RequestData, expected return err } } + + if len(expected.ImpIDs) < 1 { + return fmt.Errorf(`expected.ImpIDs must contain at least one imp ID`) + } + + opt := cmpopts.SortSlices(func(a, b string) bool { return a < b }) + if !cmp.Equal(expected.ImpIDs, actual.ImpIDs, opt) { + return fmt.Errorf(`%s actual.ImpIDs "%q" do not match expected "%q"`, description, actual.ImpIDs, expected.ImpIDs) + } return diffJson(description, actual.Body, expected.Body) } @@ -330,6 +348,9 @@ func diffBids(t *testing.T, description string, actual *adapters.TypedBid, expec assert.Equal(t, string(expected.Seat), string(actual.Seat), fmt.Sprintf(`%s.seat "%s" does not match expected "%s."`, description, string(actual.Seat), string(expected.Seat))) assert.Equal(t, string(expected.Type), string(actual.BidType), fmt.Sprintf(`%s.type "%s" does not match expected "%s."`, description, string(actual.BidType), string(expected.Type))) assert.NoError(t, diffOrtbBids(fmt.Sprintf("%s.bid", description), actual.Bid, expected.Bid)) + if expected.Video != nil { + assert.NoError(t, diffBidVideo(fmt.Sprintf("%s.video", description), actual.BidVideo, expected.Video)) + } } // diffOrtbBids compares the actual Bid made by the adapter to the expectation from the JSON file. @@ -346,6 +367,15 @@ func diffOrtbBids(description string, actual *openrtb2.Bid, expected json.RawMes return diffJson(description, actualJson, expected) } +func diffBidVideo(description string, actual *openrtb_ext.ExtBidPrebidVideo, expected json.RawMessage) error { + actualJson, err := json.Marshal(actual) + if err != nil { + return fmt.Errorf("%s failed to marshal actual Bid Video into JSON. %v", description, err) + } + + return diffJson(description, actualJson, []byte(expected)) +} + // diffJson compares two JSON byte arrays for structural equality. It will produce an error if either // byte array is not actually JSON. func diffJson(description string, actual []byte, expected []byte) error { diff --git a/adapters/adelement/adelement.go b/adapters/adelement/adelement.go new file mode 100644 index 00000000000..11b79ad37b7 --- /dev/null +++ b/adapters/adelement/adelement.go @@ -0,0 +1,141 @@ +package adelement + +import ( + "encoding/json" + "fmt" + "text/template" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +type adapter struct { + endpoint *template.Template +} + +// Builder builds a new instance of the Adelement adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + template, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + + bidder := &adapter{ + endpoint: template, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + adelementExt, err := getImpressionExt(&request.Imp[0]) + if err != nil { + return nil, []error{err} + } + + url, err := a.buildEndpointURL(adelementExt) + if err != nil { + return nil, []error{err} + } + + requestJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: url, + Body: requestJSON, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), + } + + return []*adapters.RequestData{requestData}, nil +} + +func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtAdelement, error) { + var bidderExt adapters.ExtImpBidder + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "ext.bidder not provided", + } + } + var adelementExt openrtb_ext.ExtAdelement + if err := jsonutil.Unmarshal(bidderExt.Bidder, &adelementExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "ext.bidder not provided", + } + } + imp.Ext = nil + return &adelementExt, nil +} + +func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtAdelement) (string, error) { + endpointParams := macros.EndpointTemplateParams{SupplyId: params.SupplyId} + return macros.ResolveMacros(a.endpoint, endpointParams) +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Bad Server Response", + }} + } + + if len(response.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty SeatBid array", + }} + } + + var bidErrs []error + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + bid := seatBid.Bid[i] + bidType, err := getBidType(bid) + if err != nil { + // could not determinate media type, append an error and continue with the next bid. + bidErrs = append(bidErrs, err) + continue + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + }) + } + } + return bidResponse, bidErrs +} + +func getBidType(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + // determinate media type by bid response field mtype + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupAudio: + return openrtb_ext.BidTypeAudio, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + } + + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Could not define media type for impression: %s", bid.ImpID), + } +} diff --git a/adapters/adelement/adelement_test.go b/adapters/adelement/adelement_test.go new file mode 100644 index 00000000000..2cfa599f803 --- /dev/null +++ b/adapters/adelement/adelement_test.go @@ -0,0 +1,28 @@ +package adelement + +import ( + "testing" + + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderAdelement, config.Adapter{ + Endpoint: "http://test.adelement.com/openrtb2/auction?supply_id={{.SupplyId}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 196}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "adelementtest", bidder) +} + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderAdelement, config.Adapter{ + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 196}) + + assert.Error(t, buildErr) +} diff --git a/adapters/adelement/adelementtest/exemplary/audio-app.json b/adapters/adelement/adelementtest/exemplary/audio-app.json new file mode 100644 index 00000000000..2e67d460680 --- /dev/null +++ b/adapters/adelement/adelementtest/exemplary/audio-app.json @@ -0,0 +1,91 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"ec943cb9-61ec-460f-a925-6489c3fcc4e3" + }, + "imp": [ + { + "id": "test-imp-id", + "audio": { + "mimes": ["audio/mp4"], + "protocols": [9,10] + }, + "ext": { + "bidder": { + "supply_id": "1" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.adelement.com/openrtb2/auction?supply_id=1", + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa":"ec943cb9-61ec-460f-a925-6489c3fcc4e3" + }, + "imp": [ + { + "id": "test-imp-id", + "audio": { + "mimes": ["audio/mp4"], + "protocols": [9,10] + } + } + ] + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "adelement", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "mtype": 3 + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "mtype": 3 + }, + "type": "audio" + } + ] + } + ] +} diff --git a/adapters/adelement/adelementtest/exemplary/audio-web.json b/adapters/adelement/adelementtest/exemplary/audio-web.json new file mode 100644 index 00000000000..7bb39f3cb06 --- /dev/null +++ b/adapters/adelement/adelementtest/exemplary/audio-web.json @@ -0,0 +1,91 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "buyeruid": "be5e209ad46927520000000000000000" + }, + "imp": [ + { + "id": "test-imp-id", + "audio": { + "mimes": ["audio/mp4"], + "protocols": [9,10] + }, + "ext": { + "bidder": { + "supply_id": "1" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.adelement.com/openrtb2/auction?supply_id=1", + "body": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "user": { + "buyeruid": "be5e209ad46927520000000000000000" + }, + "imp": [ + { + "id": "test-imp-id", + "audio": { + "mimes": ["audio/mp4"], + "protocols": [9,10] + } + } + ] + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "adelement", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "mtype": 3 + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "mtype": 3 + }, + "type": "audio" + } + ] + } + ] +} diff --git a/adapters/adelement/adelementtest/exemplary/banner-app.json b/adapters/adelement/adelementtest/exemplary/banner-app.json new file mode 100644 index 00000000000..c714ca7416e --- /dev/null +++ b/adapters/adelement/adelementtest/exemplary/banner-app.json @@ -0,0 +1,140 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "supply_id": "1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.adelement.com/openrtb2/auction?supply_id=1", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + }, + "impIDs": ["some-impression-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "mtype": 1 + } + ], + "seat": "adelement" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adelement": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/adelement/adelementtest/exemplary/banner-web.json b/adapters/adelement/adelementtest/exemplary/banner-web.json new file mode 100644 index 00000000000..a1706cbc323 --- /dev/null +++ b/adapters/adelement/adelementtest/exemplary/banner-web.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "banner": { + "w":320, + "h":50 + }, + "ext": { + "bidder": { + "supply_id": "1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.adelement.com/openrtb2/auction?supply_id=1", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "banner": { + "w":320, + "h":50 + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + }, + "impIDs": ["some-impression-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "mtype": 1 + } + ], + "seat": "adelement" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adelement": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 320, + "h": 50, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/bizzclick/bizzclicktest/exemplary/native-app.json b/adapters/adelement/adelementtest/exemplary/native-app.json similarity index 83% rename from adapters/bizzclick/bizzclicktest/exemplary/native-app.json rename to adapters/adelement/adelementtest/exemplary/native-app.json index 764f6006049..9b957456e6c 100644 --- a/adapters/bizzclick/bizzclicktest/exemplary/native-app.json +++ b/adapters/adelement/adelementtest/exemplary/native-app.json @@ -33,8 +33,7 @@ }, "ext": { "bidder": { - "accountId": "accountId", - "placementId": "placementId" + "supply_id": "1" } } } @@ -43,24 +42,7 @@ "httpCalls": [ { "expectedRequest": { - "headers": { - "Content-Type": [ - "application/json;charset=utf-8" - ], - "Accept": [ - "application/json" - ], - "X-Openrtb-Version": [ - "2.5" - ], - "User-Agent": [ - "test-user-agent" - ], - "X-Forwarded-For": [ - "123.123.123.123" - ] - }, - "uri": "http://us.example.com/bid?rtb_seat_id=placementId&secret_key=accountId", + "uri": "http://test.adelement.com/openrtb2/auction?supply_id=1", "body": { "id": "some-request-id", "device": { @@ -95,7 +77,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs": ["some-impression-id"] }, "mockResponse": { "status": 200, @@ -112,14 +95,20 @@ "adomain": [ "awesome.com" ], - "crid": "20" + "crid": "20", + "mtype": 4 } ], - "type": "native", - "seat": "bizzclick" + "seat": "adelement" } ], - "cur": "USD" + "cur": "USD", + "ext": { + "responsetimemillis": { + "adelement": 154 + }, + "tmaxrequest": 1000 + } } } } @@ -136,7 +125,8 @@ "crid": "20", "adomain": [ "awesome.com" - ] + ], + "mtype": 4 }, "type": "native" } diff --git a/adapters/bizzclick/bizzclicktest/exemplary/native-web.json b/adapters/adelement/adelementtest/exemplary/native-web.json similarity index 82% rename from adapters/bizzclick/bizzclicktest/exemplary/native-web.json rename to adapters/adelement/adelementtest/exemplary/native-web.json index 04a33e6f6b0..92e4eb9aa46 100644 --- a/adapters/bizzclick/bizzclicktest/exemplary/native-web.json +++ b/adapters/adelement/adelementtest/exemplary/native-web.json @@ -27,8 +27,8 @@ }, "ext": { "bidder": { - "accountId": "accountId", - "placementId": "placementId" + "host": "ep1", + "supply_id": "1" } } } @@ -37,24 +37,7 @@ "httpCalls": [ { "expectedRequest": { - "headers": { - "Content-Type": [ - "application/json;charset=utf-8" - ], - "Accept": [ - "application/json" - ], - "X-Openrtb-Version": [ - "2.5" - ], - "User-Agent": [ - "test-user-agent" - ], - "X-Forwarded-For": [ - "2607:fb90:f27:4512:d800:cb23:a603:e245" - ] - }, - "uri": "http://us.example.com/bid?rtb_seat_id=placementId&secret_key=accountId", + "uri": "http://test.adelement.com/openrtb2/auction?supply_id=1", "body": { "id": "some-request-id", "device": { @@ -83,7 +66,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs": ["some-impression-id"] }, "mockResponse": { "status": 200, @@ -100,13 +84,20 @@ "adomain": [ "awesome.com" ], - "crid": "20" + "crid": "20", + "mtype": 4 } ], - "seat": "bizzclick" + "seat": "acuityads" } ], - "cur": "USD" + "cur": "USD", + "ext": { + "responsetimemillis": { + "acuityads": 154 + }, + "tmaxrequest": 1000 + } } } } @@ -123,7 +114,8 @@ "crid": "20", "adomain": [ "awesome.com" - ] + ], + "mtype": 4 }, "type": "native" } diff --git a/adapters/adelement/adelementtest/exemplary/video-app.json b/adapters/adelement/adelementtest/exemplary/video-app.json new file mode 100644 index 00000000000..700f74341ff --- /dev/null +++ b/adapters/adelement/adelementtest/exemplary/video-app.json @@ -0,0 +1,150 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "supply_id": "1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.adelement.com/openrtb2/auction?supply_id=1", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + }, + "impIDs": ["some-impression-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "mtype": 2 + } + ], + "seat": "adelement" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adelement": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 1280, + "h": 720, + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/adelement/adelementtest/exemplary/video-web.json b/adapters/adelement/adelementtest/exemplary/video-web.json new file mode 100644 index 00000000000..0351a7ebcd2 --- /dev/null +++ b/adapters/adelement/adelementtest/exemplary/video-web.json @@ -0,0 +1,148 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "supply_id": "1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.adelement.com/openrtb2/auction?supply_id=1", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + }, + "impIDs": ["some-impression-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "mtype": 2, + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "adelement" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adelement": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "mtype": 2, + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type":"video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/adelement/adelementtest/supplemental/empty-seatbid-array.json b/adapters/adelement/adelementtest/supplemental/empty-seatbid-array.json new file mode 100644 index 00000000000..ff7a2540f87 --- /dev/null +++ b/adapters/adelement/adelementtest/supplemental/empty-seatbid-array.json @@ -0,0 +1,120 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "supply_id": "1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://test.adelement.com/openrtb2/auction?supply_id=1", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + }, + "impIDs": ["some-impression-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adelement": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "mockResponse": { + "status": 200, + "body": "invalid response" + }, + "expectedMakeBidsErrors": [ + { + "value": "Empty SeatBid array", + "comparison": "literal" + } + ] +} diff --git a/adapters/bizzclick/bizzclicktest/supplemental/invalid-bizzclick-ext-object.json b/adapters/adelement/adelementtest/supplemental/invalid-aceex-ext-object.json similarity index 100% rename from adapters/bizzclick/bizzclicktest/supplemental/invalid-bizzclick-ext-object.json rename to adapters/adelement/adelementtest/supplemental/invalid-aceex-ext-object.json diff --git a/adapters/bizzclick/bizzclicktest/supplemental/invalid-response.json b/adapters/adelement/adelementtest/supplemental/invalid-response.json similarity index 77% rename from adapters/bizzclick/bizzclicktest/supplemental/invalid-response.json rename to adapters/adelement/adelementtest/supplemental/invalid-response.json index 57f36d3a372..e70dccfdb0f 100644 --- a/adapters/bizzclick/bizzclicktest/supplemental/invalid-response.json +++ b/adapters/adelement/adelementtest/supplemental/invalid-response.json @@ -34,8 +34,7 @@ }, "ext": { "bidder": { - "accountId": "accountId", - "placementId": "placementId" + "supply_id": "1" } } } @@ -44,24 +43,7 @@ "httpCalls": [{ "expectedRequest": { - "headers": { - "Content-Type": [ - "application/json;charset=utf-8" - ], - "Accept": [ - "application/json" - ], - "X-Openrtb-Version": [ - "2.5" - ], - "User-Agent": [ - "test-user-agent" - ], - "X-Forwarded-For": [ - "123.123.123.123" - ] - }, - "uri": "http://us.example.com/bid?rtb_seat_id=placementId&secret_key=accountId", + "uri": "http://test.adelement.com/openrtb2/auction?supply_id=1", "body": { "id": "some-request-id", "device": { @@ -96,7 +78,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs": ["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/bizzclick/bizzclicktest/supplemental/status-code-bad-request.json b/adapters/adelement/adelementtest/supplemental/status-code-bad-request.json similarity index 87% rename from adapters/bizzclick/bizzclicktest/supplemental/status-code-bad-request.json rename to adapters/adelement/adelementtest/supplemental/status-code-bad-request.json index f0a3b91ae3c..008c17da1d9 100644 --- a/adapters/bizzclick/bizzclicktest/supplemental/status-code-bad-request.json +++ b/adapters/adelement/adelementtest/supplemental/status-code-bad-request.json @@ -33,8 +33,7 @@ }, "ext": { "bidder": { - "accountId": "accountId", - "placementId": "placementId" + "supply_id": "1" } } } @@ -43,7 +42,7 @@ "httpCalls": [{ "expectedRequest": { - "uri": "http://us.example.com/bid?rtb_seat_id=placementId&secret_key=accountId", + "uri": "http://test.adelement.com/openrtb2/auction?supply_id=1", "body": { "id": "some-request-id", "imp": [ @@ -77,7 +76,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs": ["some-impression-id"] }, "mockResponse": { "status": 400 @@ -86,7 +86,7 @@ "expectedBidResponses": [], "expectedMakeBidsErrors": [ { - "value": "Unexpected status code: [ 400 ]", + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", "comparison": "literal" } ] diff --git a/adapters/bizzclick/bizzclicktest/supplemental/status-code-no-content.json b/adapters/adelement/adelementtest/supplemental/status-code-no-content.json similarity index 87% rename from adapters/bizzclick/bizzclicktest/supplemental/status-code-no-content.json rename to adapters/adelement/adelementtest/supplemental/status-code-no-content.json index 62aecf58c31..eeecb9bef23 100644 --- a/adapters/bizzclick/bizzclicktest/supplemental/status-code-no-content.json +++ b/adapters/adelement/adelementtest/supplemental/status-code-no-content.json @@ -23,8 +23,7 @@ }, "ext": { "bidder": { - "accountId": "accountId", - "placementId": "placementId" + "supply_id": "1" } } }] @@ -32,7 +31,7 @@ "httpCalls": [{ "expectedRequest": { - "uri": "http://us.example.com/bid?rtb_seat_id=placementId&secret_key=accountId", + "uri": "http://test.adelement.com/openrtb2/auction?supply_id=1", "body": { "id": "some-request-id", "imp": [{ @@ -58,7 +57,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs": ["some-impression-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/bizzclick/bizzclicktest/supplemental/status-code-other-error.json b/adapters/adelement/adelementtest/supplemental/status-code-other-error.json similarity index 83% rename from adapters/bizzclick/bizzclicktest/supplemental/status-code-other-error.json rename to adapters/adelement/adelementtest/supplemental/status-code-other-error.json index 398f0b67ee0..b142d0c0796 100644 --- a/adapters/bizzclick/bizzclicktest/supplemental/status-code-other-error.json +++ b/adapters/adelement/adelementtest/supplemental/status-code-other-error.json @@ -25,8 +25,7 @@ }, "ext": { "bidder": { - "accountId": "accountId", - "placementId": "placementId" + "supply_id": "1" } } } @@ -35,7 +34,7 @@ "httpCalls": [{ "expectedRequest": { - "uri": "http://us.example.com/bid?rtb_seat_id=placementId&secret_key=accountId", + "uri": "http://test.adelement.com/openrtb2/auction?supply_id=1", "body": { "id": "some-request-id", "imp": [ @@ -63,7 +62,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs": ["some-impression-id"] }, "mockResponse": { "status": 306 @@ -72,7 +72,7 @@ "expectedBidResponses": [], "expectedMakeBidsErrors": [ { - "value": "Unexpected status code: [ 306 ]. Run with request.debug = 1 for more info", + "value": "Unexpected status code: 306. Run with request.debug = 1 for more info", "comparison": "literal" } ] diff --git a/adapters/bizzclick/bizzclicktest/supplemental/status-code-service-unavailable.json b/adapters/adelement/adelementtest/supplemental/status-code-service-unavailable.json similarity index 83% rename from adapters/bizzclick/bizzclicktest/supplemental/status-code-service-unavailable.json rename to adapters/adelement/adelementtest/supplemental/status-code-service-unavailable.json index 733b5f3f83b..545afa63b11 100644 --- a/adapters/bizzclick/bizzclicktest/supplemental/status-code-service-unavailable.json +++ b/adapters/adelement/adelementtest/supplemental/status-code-service-unavailable.json @@ -25,8 +25,7 @@ }, "ext": { "bidder": { - "accountId": "accountId", - "placementId": "placementId" + "supply_id": "1" } } } @@ -35,7 +34,7 @@ "httpCalls": [{ "expectedRequest": { - "uri": "http://us.example.com/bid?rtb_seat_id=placementId&secret_key=accountId", + "uri": "http://test.adelement.com/openrtb2/auction?supply_id=1", "body": { "id": "some-request-id", "imp": [ @@ -63,7 +62,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs": ["some-impression-id"] }, "mockResponse": { "status": 503 @@ -72,7 +72,7 @@ "expectedBidResponses": [], "expectedMakeBidsErrors": [ { - "value": "Something went wrong, please contact your Account Manager. Status Code: [ 503 ] ", + "value": "Unexpected status code: 503. Run with request.debug = 1 for more info", "comparison": "literal" } ] diff --git a/adapters/adf/adf.go b/adapters/adf/adf.go index 7ff817559cc..08fb9b8bb4a 100644 --- a/adapters/adf/adf.go +++ b/adapters/adf/adf.go @@ -5,11 +5,12 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { @@ -36,7 +37,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte for _, imp := range request.Imp { var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { errors = append(errors, &errortypes.BadInput{ Message: err.Error(), }) @@ -44,7 +45,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte } var adfImpExt openrtb_ext.ExtImpAdf - if err := json.Unmarshal(bidderExt.Bidder, &adfImpExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &adfImpExt); err != nil { errors = append(errors, &errortypes.BadInput{ Message: err.Error(), }) @@ -65,7 +66,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte var err error if len(request.Ext) > 0 { - if err = json.Unmarshal(request.Ext, &requestExt); err != nil { + if err = jsonutil.Unmarshal(request.Ext, &requestExt); err != nil { errors = append(errors, err) } } @@ -91,6 +92,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte Method: "POST", Uri: a.endpoint, Body: requestJSON, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), } return []*adapters.RequestData{requestData}, errors @@ -116,7 +118,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R } var response openrtb2.BidResponse - if err := json.Unmarshal(responseData.Body, &response); err != nil { + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { return nil, []error{err} } @@ -143,7 +145,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { if bid.Ext != nil { var bidExt openrtb_ext.ExtBid - err := json.Unmarshal(bid.Ext, &bidExt) + err := jsonutil.Unmarshal(bid.Ext, &bidExt) if err == nil && bidExt.Prebid != nil { return openrtb_ext.ParseBidType(string(bidExt.Prebid.Type)) } diff --git a/adapters/adf/adf_test.go b/adapters/adf/adf_test.go index bf8af8d6845..21e8a769c15 100644 --- a/adapters/adf/adf_test.go +++ b/adapters/adf/adf_test.go @@ -3,9 +3,9 @@ package adf import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adf/adftest/exemplary/dynamic-tag.json b/adapters/adf/adftest/exemplary/dynamic-tag.json index be366a24807..9770bc07832 100644 --- a/adapters/adf/adftest/exemplary/dynamic-tag.json +++ b/adapters/adf/adftest/exemplary/dynamic-tag.json @@ -88,7 +88,8 @@ "w": 1920, "h": 800 } - } + }, + "impIDs":["test-imp-id","test-imp-id2"] }, "mockResponse": { "status": 204 diff --git a/adapters/adf/adftest/exemplary/multi-format.json b/adapters/adf/adftest/exemplary/multi-format.json index b0e9d98f440..ffea1b185be 100644 --- a/adapters/adf/adftest/exemplary/multi-format.json +++ b/adapters/adf/adftest/exemplary/multi-format.json @@ -87,7 +87,8 @@ }, "tagid": "828783" }] - } + }, + "impIDs":["test-imp-id-1","test-imp-id-2"] }, "mockResponse": { "status": 200, diff --git a/adapters/adf/adftest/exemplary/multi-native.json b/adapters/adf/adftest/exemplary/multi-native.json index 6c9279b7db6..6b9f72b214f 100644 --- a/adapters/adf/adftest/exemplary/multi-native.json +++ b/adapters/adf/adftest/exemplary/multi-native.json @@ -55,7 +55,8 @@ }, "tagid": "828783" }] - } + }, + "impIDs":["test-imp-id-1","test-imp-id-2"] }, "mockResponse": { "status": 200, diff --git a/adapters/adf/adftest/exemplary/single-banner-pricetype-gross-extend-ext.json b/adapters/adf/adftest/exemplary/single-banner-pricetype-gross-extend-ext.json index 0cd03c75bb5..777842d3e08 100644 --- a/adapters/adf/adftest/exemplary/single-banner-pricetype-gross-extend-ext.json +++ b/adapters/adf/adftest/exemplary/single-banner-pricetype-gross-extend-ext.json @@ -73,7 +73,8 @@ "w": 1920, "h": 800 } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adf/adftest/exemplary/single-banner-pricetype-gross.json b/adapters/adf/adftest/exemplary/single-banner-pricetype-gross.json index 2fb7cf24c5a..98781cd1285 100644 --- a/adapters/adf/adftest/exemplary/single-banner-pricetype-gross.json +++ b/adapters/adf/adftest/exemplary/single-banner-pricetype-gross.json @@ -63,7 +63,8 @@ "w": 1920, "h": 800 } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adf/adftest/exemplary/single-banner-pricetype-net.json b/adapters/adf/adftest/exemplary/single-banner-pricetype-net.json index cb4b5f8a1f4..bd716bac1b3 100644 --- a/adapters/adf/adftest/exemplary/single-banner-pricetype-net.json +++ b/adapters/adf/adftest/exemplary/single-banner-pricetype-net.json @@ -63,7 +63,8 @@ "w": 1920, "h": 800 } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adf/adftest/exemplary/single-banner.json b/adapters/adf/adftest/exemplary/single-banner.json index 2fc869024c1..d4017c1e85d 100644 --- a/adapters/adf/adftest/exemplary/single-banner.json +++ b/adapters/adf/adftest/exemplary/single-banner.json @@ -56,7 +56,8 @@ "w": 1920, "h": 800 } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adf/adftest/exemplary/single-native.json b/adapters/adf/adftest/exemplary/single-native.json index 347c4aebcc6..ec3c465727c 100644 --- a/adapters/adf/adftest/exemplary/single-native.json +++ b/adapters/adf/adftest/exemplary/single-native.json @@ -52,7 +52,8 @@ "w": 1920, "h": 800 } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adf/adftest/exemplary/single-video.json b/adapters/adf/adftest/exemplary/single-video.json index 2e8c0f953e2..f6e2eb2441e 100644 --- a/adapters/adf/adftest/exemplary/single-video.json +++ b/adapters/adf/adftest/exemplary/single-video.json @@ -56,7 +56,8 @@ "w": 1920, "h": 800 } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adf/adftest/exemplary/two-banners-different-pricetypes-extend-ext.json b/adapters/adf/adftest/exemplary/two-banners-different-pricetypes-extend-ext.json index 59e380d5f2e..4e524ec1905 100644 --- a/adapters/adf/adftest/exemplary/two-banners-different-pricetypes-extend-ext.json +++ b/adapters/adf/adftest/exemplary/two-banners-different-pricetypes-extend-ext.json @@ -102,7 +102,8 @@ "w": 1920, "h": 800 } - } + }, + "impIDs":["test-imp-id","test-imp-id2"] }, "mockResponse": { "status": 200, diff --git a/adapters/adf/adftest/exemplary/two-banners-different-pricetypes.json b/adapters/adf/adftest/exemplary/two-banners-different-pricetypes.json index 918ef3ca7f3..24c8d98beae 100644 --- a/adapters/adf/adftest/exemplary/two-banners-different-pricetypes.json +++ b/adapters/adf/adftest/exemplary/two-banners-different-pricetypes.json @@ -92,7 +92,8 @@ "w": 1920, "h": 800 } - } + }, + "impIDs":["test-imp-id","test-imp-id2"] }, "mockResponse": { "status": 200, diff --git a/adapters/adf/adftest/supplemental/bad-request.json b/adapters/adf/adftest/supplemental/bad-request.json index 7424eae4656..f32f10056f8 100644 --- a/adapters/adf/adftest/supplemental/bad-request.json +++ b/adapters/adf/adftest/supplemental/bad-request.json @@ -31,7 +31,8 @@ }, "tagid": "12345" }] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400 diff --git a/adapters/adf/adftest/supplemental/empty-response.json b/adapters/adf/adftest/supplemental/empty-response.json index b2d6eab97fe..96772c03c67 100644 --- a/adapters/adf/adftest/supplemental/empty-response.json +++ b/adapters/adf/adftest/supplemental/empty-response.json @@ -31,7 +31,8 @@ }, "tagid": "12345" }] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/adf/adftest/supplemental/invalid-imp-mediatype.json b/adapters/adf/adftest/supplemental/invalid-imp-mediatype.json index 5dab8c36fa2..eb3935851cb 100644 --- a/adapters/adf/adftest/supplemental/invalid-imp-mediatype.json +++ b/adapters/adf/adftest/supplemental/invalid-imp-mediatype.json @@ -48,7 +48,8 @@ }, "tagid": "12345" }] - } + }, + "impIDs":["test-imp-id","test-imp-id-2"] }, "mockResponse": { "status": 200, diff --git a/adapters/adf/adftest/supplemental/nobid-response.json b/adapters/adf/adftest/supplemental/nobid-response.json index 57dd2d5c626..800666615dd 100644 --- a/adapters/adf/adftest/supplemental/nobid-response.json +++ b/adapters/adf/adftest/supplemental/nobid-response.json @@ -31,7 +31,8 @@ }, "tagid": "12345" }] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adf/adftest/supplemental/server-error.json b/adapters/adf/adftest/supplemental/server-error.json index 15604ad2189..1983498093e 100644 --- a/adapters/adf/adftest/supplemental/server-error.json +++ b/adapters/adf/adftest/supplemental/server-error.json @@ -31,7 +31,8 @@ }, "tagid": "12345" }] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 500, diff --git a/adapters/adf/adftest/supplemental/unparsable-response.json b/adapters/adf/adftest/supplemental/unparsable-response.json index 091f05cea22..5a89ff2dd3b 100644 --- a/adapters/adf/adftest/supplemental/unparsable-response.json +++ b/adapters/adf/adftest/supplemental/unparsable-response.json @@ -31,7 +31,8 @@ }, "tagid": "12345" }] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -42,7 +43,7 @@ "expectedBidResponses": [], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/adf/params_test.go b/adapters/adf/params_test.go index 0b05519df3b..9b3fd7b3e31 100644 --- a/adapters/adf/params_test.go +++ b/adapters/adf/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // This file actually intends to test static/bidder-params/adf.json diff --git a/adapters/adgeneration/adgeneration.go b/adapters/adgeneration/adgeneration.go index a2a10ed51f2..31e27b862a9 100644 --- a/adapters/adgeneration/adgeneration.go +++ b/adapters/adgeneration/adgeneration.go @@ -1,7 +1,6 @@ package adgeneration import ( - "encoding/json" "errors" "fmt" "net/http" @@ -10,11 +9,12 @@ import ( "strconv" "strings" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type AdgenerationAdapter struct { @@ -77,6 +77,7 @@ func (adg *AdgenerationAdapter) MakeRequests(request *openrtb2.BidRequest, reqIn Uri: bidRequestUri, Body: nil, Headers: headers, + ImpIDs: []string{request.Imp[index].ID}, } bidRequestArray = append(bidRequestArray, bidRequest) } @@ -148,10 +149,10 @@ func (adg *AdgenerationAdapter) getRawQuery(id string, request *openrtb2.BidRequ func unmarshalExtImpAdgeneration(imp *openrtb2.Imp) (*openrtb_ext.ExtImpAdgeneration, error) { var bidderExt adapters.ExtImpBidder var adgExt openrtb_ext.ExtImpAdgeneration - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, err } - if err := json.Unmarshal(bidderExt.Bidder, &adgExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &adgExt); err != nil { return nil, err } if adgExt.Id == "" { @@ -202,7 +203,7 @@ func (adg *AdgenerationAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e }} } var bidResp adgServerResponse - err := json.Unmarshal(response.Body, &bidResp) + err := jsonutil.Unmarshal(response.Body, &bidResp) if err != nil { return nil, []error{err} } diff --git a/adapters/adgeneration/adgeneration_test.go b/adapters/adgeneration/adgeneration_test.go index c204fbd320d..e12ed431f04 100644 --- a/adapters/adgeneration/adgeneration_test.go +++ b/adapters/adgeneration/adgeneration_test.go @@ -4,11 +4,11 @@ import ( "encoding/json" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -259,6 +259,7 @@ func checkBidResponse(t *testing.T, bidderResponse *adapters.BidderResponse, exp var expectedCrID string = "Dummy_supership.jp" var extectedDealID string = "test-deal-id" + //nolint: staticcheck // false positive SA5011: possible nil pointer dereference assert.Equal(t, expectedCurrency, bidderResponse.Currency) assert.Equal(t, 1, len(bidderResponse.Bids)) assert.Equal(t, expectedID, bidderResponse.Bids[0].Bid.ID) diff --git a/adapters/adgeneration/adgenerationtest/exemplary/single-banner-android.json b/adapters/adgeneration/adgenerationtest/exemplary/single-banner-android.json index 487d44692e0..1753abc7127 100644 --- a/adapters/adgeneration/adgenerationtest/exemplary/single-banner-android.json +++ b/adapters/adgeneration/adgenerationtest/exemplary/single-banner-android.json @@ -75,7 +75,8 @@ "X-Forwarded-For": [ "0.0.0.0" ] - } + }, + "impIDs":["some-impression-id"] }, "mockResponse":{ "status": 200, diff --git a/adapters/adgeneration/adgenerationtest/exemplary/single-banner-ios.json b/adapters/adgeneration/adgenerationtest/exemplary/single-banner-ios.json index e0ff64c453d..836cfde35c0 100644 --- a/adapters/adgeneration/adgenerationtest/exemplary/single-banner-ios.json +++ b/adapters/adgeneration/adgenerationtest/exemplary/single-banner-ios.json @@ -75,7 +75,8 @@ "X-Forwarded-For": [ "0.0.0.0" ] - } + }, + "impIDs":["some-impression-id"] }, "mockResponse":{ "status": 200, diff --git a/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json b/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json index cd68e32cd09..1ad91f8fabf 100644 --- a/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json +++ b/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json @@ -73,7 +73,8 @@ "X-Forwarded-For": [ "0.0.0.0" ] - } + }, + "impIDs":["some-impression-id"] }, "mockResponse":{ "status": 200, diff --git a/adapters/adgeneration/adgenerationtest/supplemental/204-bid-response.json b/adapters/adgeneration/adgenerationtest/supplemental/204-bid-response.json index 8d1ab442154..8adf7442976 100644 --- a/adapters/adgeneration/adgenerationtest/supplemental/204-bid-response.json +++ b/adapters/adgeneration/adgenerationtest/supplemental/204-bid-response.json @@ -66,7 +66,8 @@ "User-Agent": [ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36" ] - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/adgeneration/adgenerationtest/supplemental/400-bid-response.json b/adapters/adgeneration/adgenerationtest/supplemental/400-bid-response.json index dc7c41d0f6b..c4459aca972 100644 --- a/adapters/adgeneration/adgenerationtest/supplemental/400-bid-response.json +++ b/adapters/adgeneration/adgenerationtest/supplemental/400-bid-response.json @@ -66,7 +66,8 @@ "User-Agent": [ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36" ] - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/adgeneration/adgenerationtest/supplemental/no-bid-response.json b/adapters/adgeneration/adgenerationtest/supplemental/no-bid-response.json index 523806a9f3c..58ed36e5d23 100644 --- a/adapters/adgeneration/adgenerationtest/supplemental/no-bid-response.json +++ b/adapters/adgeneration/adgenerationtest/supplemental/no-bid-response.json @@ -66,7 +66,8 @@ "User-Agent": [ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36" ] - } + }, + "impIDs":["some-impression-id"] }, "mockResponse":{ "status": 200, diff --git a/adapters/adgeneration/params_test.go b/adapters/adgeneration/params_test.go index 062d122ac08..bdef4ec1d05 100644 --- a/adapters/adgeneration/params_test.go +++ b/adapters/adgeneration/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/adhese/adhese.go b/adapters/adhese/adhese.go index 9c84676c379..181cc661e46 100644 --- a/adapters/adhese/adhese.go +++ b/adapters/adhese/adhese.go @@ -10,12 +10,13 @@ import ( "strings" "text/template" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type AdheseAdapter struct { @@ -32,7 +33,7 @@ func extractTargetParameters(parameters openrtb_ext.ExtImpAdhese) string { } var parametersAsString = "" var targetParsed map[string]interface{} - err := json.Unmarshal(parameters.Keywords, &targetParsed) + err := jsonutil.Unmarshal(parameters.Keywords, &targetParsed) if err != nil { return "" } @@ -59,7 +60,7 @@ func extractTargetParameters(parameters openrtb_ext.ExtImpAdhese) string { func extractGdprParameter(request *openrtb2.BidRequest) string { if request.User != nil { var extUser openrtb_ext.ExtUser - if err := json.Unmarshal(request.User.Ext, &extUser); err == nil { + if err := jsonutil.Unmarshal(request.User.Ext, &extUser); err == nil { return "/xt" + extUser.Consent } } @@ -94,13 +95,13 @@ func (a *AdheseAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adap var imp = &request.Imp[0] var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { errs = append(errs, WrapReqError("Request could not be parsed as ExtImpBidder due to: "+err.Error())) return nil, errs } var params openrtb_ext.ExtImpAdhese - if err := json.Unmarshal(bidderExt.Bidder, ¶ms); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, ¶ms); err != nil { errs = append(errs, WrapReqError("Request could not be parsed as ExtImpAdhese due to: "+err.Error())) return nil, errs } @@ -124,6 +125,7 @@ func (a *AdheseAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adap return []*adapters.RequestData{{ Method: "GET", Uri: complete_url, + ImpIDs: []string{imp.ID}, }}, errs } @@ -137,20 +139,23 @@ func (a *AdheseAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalR var bidResponse openrtb2.BidResponse var adheseBidResponseArray []AdheseBid - if err := json.Unmarshal(response.Body, &adheseBidResponseArray); err != nil { + if err := jsonutil.Unmarshal(response.Body, &adheseBidResponseArray); err != nil { return nil, []error{err, WrapServerError(fmt.Sprintf("Response %v could not be parsed as generic Adhese bid.", string(response.Body)))} } + if len(adheseBidResponseArray) == 0 { + return nil, nil + } var adheseBid = adheseBidResponseArray[0] if adheseBid.Origin == "JERLICIA" { var extArray []AdheseExt var originDataArray []AdheseOriginData - if err := json.Unmarshal(response.Body, &extArray); err != nil { + if err := jsonutil.Unmarshal(response.Body, &extArray); err != nil { return nil, []error{err, WrapServerError(fmt.Sprintf("Response %v could not be parsed to JERLICIA ext.", string(response.Body)))} } - if err := json.Unmarshal(response.Body, &originDataArray); err != nil { + if err := jsonutil.Unmarshal(response.Body, &originDataArray); err != nil { return nil, []error{err, WrapServerError(fmt.Sprintf("Response %v could not be parsed to JERLICIA origin data.", string(response.Body)))} } bidResponse = convertAdheseBid(adheseBid, extArray[0], originDataArray[0]) diff --git a/adapters/adhese/adhese_test.go b/adapters/adhese/adhese_test.go index d09a29ee9bd..b8abd82a06f 100644 --- a/adapters/adhese/adhese_test.go +++ b/adapters/adhese/adhese_test.go @@ -3,9 +3,9 @@ package adhese import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adhese/adhesetest/exemplary/banner-internal.json b/adapters/adhese/adhesetest/exemplary/banner-internal.json index 50efe4a656d..4d9b2d80093 100644 --- a/adapters/adhese/adhesetest/exemplary/banner-internal.json +++ b/adapters/adhese/adhesetest/exemplary/banner-internal.json @@ -45,7 +45,8 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads-demo.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xtdummy/xzdum-my" + "uri": "https://ads-demo.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xtdummy/xzdum-my", + "impIDs":["test-req"] }, "mockResponse": { "status": 200, diff --git a/adapters/adhese/adhesetest/exemplary/banner-market.json b/adapters/adhese/adhesetest/exemplary/banner-market.json index 340e6d87223..97240c67fda 100644 --- a/adapters/adhese/adhesetest/exemplary/banner-market.json +++ b/adapters/adhese/adhesetest/exemplary/banner-market.json @@ -38,7 +38,8 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads-market.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xfdummy" + "uri": "https://ads-market.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xfdummy", + "impIDs":["test-req"] }, "mockResponse": { "status": 200, diff --git a/adapters/adhese/adhesetest/exemplary/banner-video-internal.json b/adapters/adhese/adhesetest/exemplary/banner-video-internal.json index bd9203c2509..72182bea90c 100644 --- a/adapters/adhese/adhesetest/exemplary/banner-video-internal.json +++ b/adapters/adhese/adhesetest/exemplary/banner-video-internal.json @@ -42,7 +42,8 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads-demo.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xtdummy" + "uri": "https://ads-demo.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xtdummy", + "impIDs":["test-req"] }, "mockResponse": { "status": 200, diff --git a/adapters/adhese/adhesetest/exemplary/video.json b/adapters/adhese/adhesetest/exemplary/video.json index 134e3add52d..2c77d7f2229 100644 --- a/adapters/adhese/adhesetest/exemplary/video.json +++ b/adapters/adhese/adhesetest/exemplary/video.json @@ -1,6 +1,7 @@ { "mockBidRequest": { "imp": [{ + "id":"test-imp-id", "ext": { "bidder": { "account": "demo", @@ -20,7 +21,8 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads-demo.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall" + "uri": "https://ads-demo.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall", + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adhese/adhesetest/supplemental/req-invalid-empty-imp-ext.json b/adapters/adhese/adhesetest/supplemental/req-invalid-empty-imp-ext.json index e98823b2914..9169847be35 100644 --- a/adapters/adhese/adhesetest/supplemental/req-invalid-empty-imp-ext.json +++ b/adapters/adhese/adhesetest/supplemental/req-invalid-empty-imp-ext.json @@ -31,8 +31,8 @@ "expectedBidResponses": [], "expectedMakeRequestsErrors": [ { - "value": "Request could not be parsed as ExtImpAdhese due to: unexpected end of JSON input", - "comparison": "literal" + "value": "Request could not be parsed as ExtImpAdhese due to: expect { or n, but found", + "comparison": "startswith" } ] } \ No newline at end of file diff --git a/adapters/adhese/adhesetest/supplemental/req-invalid-no-imp-ext.json b/adapters/adhese/adhesetest/supplemental/req-invalid-no-imp-ext.json index bcb891719ae..76f5315c1cb 100644 --- a/adapters/adhese/adhesetest/supplemental/req-invalid-no-imp-ext.json +++ b/adapters/adhese/adhesetest/supplemental/req-invalid-no-imp-ext.json @@ -30,8 +30,8 @@ "expectedBidResponses": [], "expectedMakeRequestsErrors": [ { - "value": "Request could not be parsed as ExtImpBidder due to: unexpected end of JSON input", - "comparison": "literal" + "value": "Request could not be parsed as ExtImpBidder due to: expect { or n, but found", + "comparison": "startswith" } ] } \ No newline at end of file diff --git a/adapters/adhese/adhesetest/supplemental/res-invalid-height.json b/adapters/adhese/adhesetest/supplemental/res-invalid-height.json index 1e11df2f4c4..4c7989cb56b 100644 --- a/adapters/adhese/adhesetest/supplemental/res-invalid-height.json +++ b/adapters/adhese/adhesetest/supplemental/res-invalid-height.json @@ -38,7 +38,8 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads-market.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xfdummy" + "uri": "https://ads-market.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xfdummy", + "impIDs":["test-req"] }, "mockResponse": { "status": 200, @@ -81,7 +82,7 @@ "expectedBidResponses": [], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal number into Go struct field AdheseBid.height of type string", + "value": "cannot unmarshal adhese.AdheseBid.Height: expects \" or n, but found 9", "comparison": "literal" }, { "value": "Response (.*\n.*)+ could not be parsed as generic Adhese bid", diff --git a/adapters/adhese/adhesetest/supplemental/res-invalid-no-body.json b/adapters/adhese/adhesetest/supplemental/res-invalid-no-body.json index f970476450f..1ec91d9ad0a 100644 --- a/adapters/adhese/adhesetest/supplemental/res-invalid-no-body.json +++ b/adapters/adhese/adhesetest/supplemental/res-invalid-no-body.json @@ -42,7 +42,8 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads-demo.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xtdummy" + "uri": "https://ads-demo.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xtdummy", + "impIDs":["test-req"] }, "mockResponse": { "status": 200 @@ -52,8 +53,8 @@ "expectedBidResponses": [], "expectedMakeBidsErrors": [ { - "value": "unexpected end of JSON input", - "comparison": "literal" + "value": "decode slice: expect [ or n, but found", + "comparison": "startswith" },{ "value": "Response could not be parsed as generic Adhese bid.", "comparison": "literal" diff --git a/adapters/adhese/adhesetest/supplemental/res-invalid-no-origin.json b/adapters/adhese/adhesetest/supplemental/res-invalid-no-origin.json index 636e30741bb..886849bde46 100644 --- a/adapters/adhese/adhesetest/supplemental/res-invalid-no-origin.json +++ b/adapters/adhese/adhesetest/supplemental/res-invalid-no-origin.json @@ -42,7 +42,8 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads-demo.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xtdummy" + "uri": "https://ads-demo.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xtdummy", + "impIDs":["test-req"] }, "mockResponse": { "status": 200, diff --git a/adapters/adhese/adhesetest/supplemental/res-invalid-price.json b/adapters/adhese/adhesetest/supplemental/res-invalid-price.json index f23bf0f7cb1..7fcbc298876 100644 --- a/adapters/adhese/adhesetest/supplemental/res-invalid-price.json +++ b/adapters/adhese/adhesetest/supplemental/res-invalid-price.json @@ -38,7 +38,8 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads-market.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xfdummy" + "uri": "https://ads-market.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xfdummy", + "impIDs":["test-req"] }, "mockResponse": { "status": 200, @@ -81,7 +82,7 @@ "expectedBidResponses": [], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal number into Go struct field CPMValues.*\\.amount of type string", + "value": "cannot unmarshal adhese.CPMValues.Amount: expects \" or n, but found 1", "comparison": "regex" }, { "value": "Response (.*\n.*)+ could not be parsed as generic Adhese bid", diff --git a/adapters/adhese/adhesetest/supplemental/res-invalid-status-not-ok.json b/adapters/adhese/adhesetest/supplemental/res-invalid-status-not-ok.json index 96f17b15b32..4234986dd68 100644 --- a/adapters/adhese/adhesetest/supplemental/res-invalid-status-not-ok.json +++ b/adapters/adhese/adhesetest/supplemental/res-invalid-status-not-ok.json @@ -42,7 +42,8 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads-demo.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xtdummy" + "uri": "https://ads-demo.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xtdummy", + "impIDs":["test-req"] }, "mockResponse": { "status": 501, diff --git a/adapters/adhese/adhesetest/supplemental/res-invalid-width.json b/adapters/adhese/adhesetest/supplemental/res-invalid-width.json index 77b23d347af..139a5c598a1 100644 --- a/adapters/adhese/adhesetest/supplemental/res-invalid-width.json +++ b/adapters/adhese/adhesetest/supplemental/res-invalid-width.json @@ -38,7 +38,8 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads-market.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xfdummy" + "uri": "https://ads-market.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xfdummy", + "impIDs":["test-req"] }, "mockResponse": { "status": 200, @@ -81,7 +82,7 @@ "expectedBidResponses": [], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal number into Go struct field AdheseBid.width of type string", + "value": "cannot unmarshal adhese.AdheseBid.Width: expects \" or n, but found 7", "comparison": "literal" }, { "value": "Response (.*\n.*)+ could not be parsed as generic Adhese bid", diff --git a/adapters/adhese/adhesetest/supplemental/res-no_bids_200.json b/adapters/adhese/adhesetest/supplemental/res-no_bids_200.json new file mode 100644 index 00000000000..f8b0b5f17cd --- /dev/null +++ b/adapters/adhese/adhesetest/supplemental/res-no_bids_200.json @@ -0,0 +1,55 @@ +{ + "mockBidRequest": { + "id": "test-req", + "user": { + "ext": { + "consent" : "dummy" + } + }, + "imp": [ + { + "id": "test-req", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "account": "demo", + "location": "_adhese_prebid_demo_", + "format": "leaderboard", + "targets": + { + "ci": ["gent", "brussels"], + "ag": ["55"], + "tl": ["all"] + } + } + } + } + ], + "site": { + "id": "test", + "publisher": { + "id": "123" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ads-demo.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xtdummy", + "impIDs":["test-req"] + }, + "mockResponse": { + "status": 200, + "body": [] + } + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/adhese/adhesetest/supplemental/res-no_bids.json b/adapters/adhese/adhesetest/supplemental/res-no_bids_204.json similarity index 91% rename from adapters/adhese/adhesetest/supplemental/res-no_bids.json rename to adapters/adhese/adhesetest/supplemental/res-no_bids_204.json index c9630049f02..e678406a75f 100644 --- a/adapters/adhese/adhesetest/supplemental/res-no_bids.json +++ b/adapters/adhese/adhesetest/supplemental/res-no_bids_204.json @@ -42,7 +42,8 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads-demo.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xtdummy" + "uri": "https://ads-demo.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xtdummy", + "impIDs":["test-req"] }, "mockResponse": { "status": 204, diff --git a/adapters/adhese/adhesetest/supplemental/res-no_impression_counter.json b/adapters/adhese/adhesetest/supplemental/res-no_impression_counter.json index 99af5671f4b..20539464aa5 100644 --- a/adapters/adhese/adhesetest/supplemental/res-no_impression_counter.json +++ b/adapters/adhese/adhesetest/supplemental/res-no_impression_counter.json @@ -42,7 +42,8 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://ads-demo.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xtdummy" + "uri": "https://ads-demo.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xtdummy", + "impIDs":["test-req"] }, "mockResponse": { "status": 200, diff --git a/adapters/adhese/params_test.go b/adapters/adhese/params_test.go index 45024749b2d..b8ca161d57d 100644 --- a/adapters/adhese/params_test.go +++ b/adapters/adhese/params_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/adhese/utils.go b/adapters/adhese/utils.go index 4a6c79c8e94..a6b7d7aae8d 100644 --- a/adapters/adhese/utils.go +++ b/adapters/adhese/utils.go @@ -1,6 +1,6 @@ package adhese -import "github.com/prebid/openrtb/v19/openrtb2" +import "github.com/prebid/openrtb/v20/openrtb2" type AdheseOriginData struct { Priority string `json:"priority"` diff --git a/adapters/adkernel/adkernel.go b/adapters/adkernel/adkernel.go index 5fae001d7dd..443da423fc9 100644 --- a/adapters/adkernel/adkernel.go +++ b/adapters/adkernel/adkernel.go @@ -5,14 +5,24 @@ import ( "fmt" "net/http" "strconv" + "strings" "text/template" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +const ( + mf_suffix = "__mf" + mf_suffix_banner = "b" + mf_suffix + mf_suffix_video = "v" + mf_suffix + mf_suffix_audio = "a" + mf_suffix + mf_suffix_native = "n" + mf_suffix ) type adkernelAdapter struct { @@ -78,9 +88,6 @@ func validateImpression(imp *openrtb2.Imp, impExt *openrtb_ext.ExtImpAdkernel) e if impExt.ZoneId < 1 { return newBadInputError(fmt.Sprintf("Invalid zoneId value: %d. Ignoring imp id=%s", impExt.ZoneId, imp.ID)) } - if imp.Video == nil && imp.Banner == nil && imp.Native == nil { - return newBadInputError(fmt.Sprintf("Invalid imp id=%s. Expected imp.banner / imp.video / imp.native", imp.ID)) - } return nil } @@ -90,65 +97,86 @@ func dispatchImpressions(imps []openrtb2.Imp, impsExt []openrtb_ext.ExtImpAdkern errors := make([]error, 0) for idx := range imps { imp := imps[idx] - err := compatImpression(&imp) - if err != nil { - errors = append(errors, err) - continue - } + imp.Ext = nil impExt := impsExt[idx] if res[impExt] == nil { res[impExt] = make([]openrtb2.Imp, 0) } - res[impExt] = append(res[impExt], imp) + if isMultiFormatImp(&imp) { + splImps := splitMultiFormatImp(&imp) + res[impExt] = append(res[impExt], splImps...) + } else { + res[impExt] = append(res[impExt], imp) + } } return res, errors } -// Alter impression info to comply with adkernel platform requirements -func compatImpression(imp *openrtb2.Imp) error { - imp.Ext = nil //do not forward ext to adkernel platform - if imp.Banner != nil { - return compatBannerImpression(imp) - } +func isMultiFormatImp(imp *openrtb2.Imp) bool { + count := 0 if imp.Video != nil { - return compatVideoImpression(imp) + count++ + } + if imp.Audio != nil { + count++ + } + if imp.Banner != nil { + count++ } if imp.Native != nil { - return compatNativeImpression(imp) + count++ } - return newBadInputError("Invalid impression") + return count > 1 } -func compatBannerImpression(imp *openrtb2.Imp) error { - imp.Audio = nil - imp.Video = nil - imp.Native = nil - return nil -} +func splitMultiFormatImp(imp *openrtb2.Imp) []openrtb2.Imp { + splitImps := make([]openrtb2.Imp, 0, 4) + if imp.Banner != nil { + impCopy := *imp + impCopy.Video = nil + impCopy.Native = nil + impCopy.Audio = nil + impCopy.ID += mf_suffix_banner + splitImps = append(splitImps, impCopy) + } + if imp.Video != nil { + impCopy := *imp + impCopy.Banner = nil + impCopy.Native = nil + impCopy.Audio = nil + impCopy.ID += mf_suffix_video + splitImps = append(splitImps, impCopy) + } -func compatVideoImpression(imp *openrtb2.Imp) error { - imp.Banner = nil - imp.Audio = nil - imp.Native = nil - return nil -} + if imp.Native != nil { + impCopy := *imp + impCopy.Banner = nil + impCopy.Video = nil + impCopy.Audio = nil + impCopy.ID += mf_suffix_native + splitImps = append(splitImps, impCopy) + } -func compatNativeImpression(imp *openrtb2.Imp) error { - imp.Banner = nil - imp.Audio = nil - imp.Video = nil - return nil + if imp.Audio != nil { + impCopy := *imp + impCopy.Banner = nil + impCopy.Video = nil + impCopy.Native = nil + impCopy.ID += mf_suffix_audio + splitImps = append(splitImps, impCopy) + } + return splitImps } func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpAdkernel, error) { var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ Message: err.Error(), } } var adkernelExt openrtb_ext.ExtImpAdkernel - if err := json.Unmarshal(bidderExt.Bidder, &adkernelExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &adkernelExt); err != nil { return nil, &errortypes.BadInput{ Message: err.Error(), } @@ -177,7 +205,8 @@ func (adapter *adkernelAdapter) buildAdapterRequest(prebidBidRequest *openrtb2.B Method: "POST", Uri: url, Body: reqJSON, - Headers: headers}, nil + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(imps)}, nil } func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpAdkernel, imps []openrtb2.Imp) *openrtb2.BidRequest { @@ -215,7 +244,7 @@ func (adapter *adkernelAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e } } var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{ newBadServerResponseError(fmt.Sprintf("Bad server response: %d", err)), } @@ -232,22 +261,38 @@ func (adapter *adkernelAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e bidResponse.Currency = bidResp.Cur for i := 0; i < len(seatBid.Bid); i++ { bid := seatBid.Bid[i] + if strings.HasSuffix(bid.ImpID, mf_suffix) { + sfxStart := len(bid.ImpID) - len(mf_suffix) - 1 + bid.ImpID = bid.ImpID[:sfxStart] + } + bidType, err := getMediaTypeForBid(&bid) + if err != nil { + return nil, []error{err} + } bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &bid, - BidType: getMediaTypeForImpID(bid.ImpID, internalRequest.Imp), + BidType: bidType, }) } return bidResponse, nil } // getMediaTypeForImp figures out which media type this bid is for -func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { - for _, imp := range imps { - if imp.ID == impID && imp.Banner != nil { - return openrtb_ext.BidTypeBanner +func getMediaTypeForBid(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupAudio: + return openrtb_ext.BidTypeAudio, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + default: + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unsupported MType %d", bid.MType), } } - return openrtb_ext.BidTypeVideo } func newBadInputError(message string) error { diff --git a/adapters/adkernel/adkernel_test.go b/adapters/adkernel/adkernel_test.go index ae35f712400..f874e92d57e 100644 --- a/adapters/adkernel/adkernel_test.go +++ b/adapters/adkernel/adkernel_test.go @@ -3,15 +3,15 @@ package adkernel import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdkernel, config.Adapter{ - Endpoint: "https://pbs.adksrv.com/hb?zone={{.ZoneID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + Endpoint: "http://pbs.adksrv.com/hb?zone={{.ZoneID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/adkernel/adkerneltest/exemplary/multiformat-impression.json b/adapters/adkernel/adkerneltest/exemplary/multiformat-impression.json index b8fd88dcdae..de40a0e94a1 100644 --- a/adapters/adkernel/adkerneltest/exemplary/multiformat-impression.json +++ b/adapters/adkernel/adkerneltest/exemplary/multiformat-impression.json @@ -14,6 +14,10 @@ "banner" : { "format": [{"w": 300,"h": 250}] }, + "native" : { + "request": "{native: \"request\"}", + "ver": "1.2" + }, "ext": { "bidder": { "zoneId": 102, @@ -36,15 +40,30 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://pbs.adksrv.com/hb?zone=102", + "uri": "http://pbs.adksrv.com/hb?zone=102", "body": { "id": "0000000000001", "imp": [ { - "id": "multi-adunit", + "id": "multi-adunitb__mf", "banner": { "format": [{"w": 300, "h": 250}] } + }, + { + "id": "multi-adunitv__mf", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480 + } + }, + { + "id": "multi-adunitn__mf", + "native" : { + "request": "{native: \"request\"}", + "ver": "1.2" + } } ], "app": { @@ -54,7 +73,8 @@ "user": { "buyeruid": "A-38327932832" } - } + }, + "impIDs":["multi-adunitb__mf", "multi-adunitv__mf", "multi-adunitn__mf"] }, "mockResponse": { "status": 200, @@ -65,12 +85,39 @@ "bid": [ { "id": "bid02", - "impid": "multi-adunit", + "impid": "multi-adunitb__mf", "price": 2.25, "cid": "1001", "crid": "2002", "adid": "2002", "adm": "", + "mtype": 1, + "adomain": [ + "tag-example.com" + ] + }, + { + "id": "bid03", + "impid": "multi-adunitn__mf", + "price": 1.25, + "cid": "601", + "crid": "702", + "adid": "702", + "adm": "", + "mtype": 4, + "adomain": [ + "tag-example.com" + ] + }, + { + "id": "bid04", + "impid": "multi-adunita__mf", + "price": 1.0, + "cid": "161", + "crid": "172", + "adid": "172", + "nurl": "http://adkernel.com/win?f=nurl", + "mtype": 3, "adomain": [ "tag-example.com" ] @@ -98,9 +145,42 @@ ], "cid": "1001", "adid": "2002", - "crid": "2002" + "crid": "2002", + "mtype": 1 }, "type": "banner" + }, + { + "bid": { + "id": "bid03", + "impid": "multi-adunit", + "price": 1.25, + "adm": "", + "adomain": [ + "tag-example.com" + ], + "cid": "601", + "crid": "702", + "adid": "702", + "mtype": 4 + }, + "type": "native" + }, + { + "bid": { + "id": "bid04", + "impid": "multi-adunit", + "price": 1.0, + "nurl": "http://adkernel.com/win?f=nurl", + "adomain": [ + "tag-example.com" + ], + "cid": "161", + "crid": "172", + "adid": "172", + "mtype": 3 + }, + "type": "audio" } ] } diff --git a/adapters/adkernel/adkerneltest/exemplary/single-banner-impression.json b/adapters/adkernel/adkerneltest/exemplary/single-banner-impression.json index 24f86378fe5..439b60df5c2 100644 --- a/adapters/adkernel/adkerneltest/exemplary/single-banner-impression.json +++ b/adapters/adkernel/adkerneltest/exemplary/single-banner-impression.json @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://pbs.adksrv.com/hb?zone=101", + "uri": "http://pbs.adksrv.com/hb?zone=101", "body": { "id": "0000000000001", "imp": [ @@ -48,7 +48,8 @@ "buyeruid": "A-38327932832" }, "cur": ["TYR"] - } + }, + "impIDs":["adunit-1"] }, "mockResponse": { "status": 200, @@ -64,6 +65,7 @@ "adid": "19005", "adm": "", "cat": ["IAB2"], + "mtype": 1, "adomain": ["test.com"], "h": 250, "w": 300 @@ -92,7 +94,8 @@ "crid": "19005", "w": 300, "h": 250, - "cat": ["IAB2"] + "cat": ["IAB2"], + "mtype": 1 }, "type": "banner" } diff --git a/adapters/adkernel/adkerneltest/exemplary/single-video-impression.json b/adapters/adkernel/adkerneltest/exemplary/single-video-impression.json index b13d974616e..f98d1747bce 100644 --- a/adapters/adkernel/adkerneltest/exemplary/single-video-impression.json +++ b/adapters/adkernel/adkerneltest/exemplary/single-video-impression.json @@ -33,7 +33,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://pbs.adksrv.com/hb?zone=102", + "uri": "http://pbs.adksrv.com/hb?zone=102", "body": { "id": "0000000000001", "imp": [ @@ -55,7 +55,8 @@ "user": { "buyeruid": "A-38327932832" } - } + }, + "impIDs":["video-adunit-2"] }, "mockResponse": { "status": 200, @@ -75,6 +76,7 @@ "cat": [ "IAB2" ], + "mtype": 2, "adomain": [ "video-example.com" ] @@ -105,7 +107,8 @@ "crid": "2002", "cat": [ "IAB2" - ] + ], + "mtype": 2 }, "type": "video" } diff --git a/adapters/adkernelAdn/adkernelAdn.go b/adapters/adkernelAdn/adkernelAdn.go index 45e9e41c10c..ffee88250ca 100644 --- a/adapters/adkernelAdn/adkernelAdn.go +++ b/adapters/adkernelAdn/adkernelAdn.go @@ -7,12 +7,13 @@ import ( "strconv" "text/template" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adkernelAdnAdapter struct { @@ -124,7 +125,7 @@ func compatBannerImpression(imp *openrtb2.Imp) error { //As banner.w/h are required fields for adkernelAdn platform - take the first format entry if banner.W == nil && banner.H == nil { if len(banner.Format) == 0 { - return newBadInputError(fmt.Sprintf("Expected at least one banner.format entry or explicit w/h")) + return newBadInputError("Expected at least one banner.format entry or explicit w/h") } format := banner.Format[0] banner.Format = banner.Format[1:] @@ -149,13 +150,13 @@ func compatVideoImpression(imp *openrtb2.Imp) error { func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpAdkernelAdn, error) { var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ Message: err.Error(), } } var adkernelAdnExt openrtb_ext.ExtImpAdkernelAdn - if err := json.Unmarshal(bidderExt.Bidder, &adkernelAdnExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &adkernelAdnExt); err != nil { return nil, &errortypes.BadInput{ Message: err.Error(), } @@ -184,7 +185,8 @@ func (adapter *adkernelAdnAdapter) buildAdapterRequest(prebidBidRequest *openrtb Method: "POST", Uri: url, Body: reqJSON, - Headers: headers}, nil + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(imps)}, nil } func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpAdkernelAdn, imps []openrtb2.Imp) *openrtb2.BidRequest { @@ -225,7 +227,7 @@ func (adapter *adkernelAdnAdapter) MakeBids(internalRequest *openrtb2.BidRequest } } var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{ newBadServerResponseError(fmt.Sprintf("Bad server response: %d", err)), } diff --git a/adapters/adkernelAdn/adkernelAdn_test.go b/adapters/adkernelAdn/adkernelAdn_test.go index 651d82be3b6..f99da329403 100644 --- a/adapters/adkernelAdn/adkernelAdn_test.go +++ b/adapters/adkernelAdn/adkernelAdn_test.go @@ -3,9 +3,9 @@ package adkernelAdn import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adkernelAdn/adkerneladntest/exemplary/multiformat-impression.json b/adapters/adkernelAdn/adkerneladntest/exemplary/multiformat-impression.json index 2a6db14d540..dc20bfa0df8 100644 --- a/adapters/adkernelAdn/adkerneladntest/exemplary/multiformat-impression.json +++ b/adapters/adkernelAdn/adkerneladntest/exemplary/multiformat-impression.json @@ -55,7 +55,8 @@ "user": { "buyeruid": "A-38327932832" } - } + }, + "impIDs":["multi-adunit"] }, "mockResponse": { "status": 200, diff --git a/adapters/adkernelAdn/adkerneladntest/exemplary/single-banner-impression.json b/adapters/adkernelAdn/adkerneladntest/exemplary/single-banner-impression.json index 6df3e0f16e3..d05abb04ed2 100644 --- a/adapters/adkernelAdn/adkerneladntest/exemplary/single-banner-impression.json +++ b/adapters/adkernelAdn/adkerneladntest/exemplary/single-banner-impression.json @@ -48,7 +48,8 @@ "user": { "buyeruid": "A-38327932832" } - } + }, + "impIDs":["adunit-1"] }, "mockResponse": { "status": 200, diff --git a/adapters/adkernelAdn/adkerneladntest/exemplary/single-video-impression.json b/adapters/adkernelAdn/adkerneladntest/exemplary/single-video-impression.json index e60aa06ca1f..d57213630c7 100644 --- a/adapters/adkernelAdn/adkerneladntest/exemplary/single-video-impression.json +++ b/adapters/adkernelAdn/adkerneladntest/exemplary/single-video-impression.json @@ -55,7 +55,8 @@ "user": { "buyeruid": "A-38327932832" } - } + }, + "impIDs":["video-adunit-2"] }, "mockResponse": { "status": 200, diff --git a/adapters/adkernelAdn/adkerneladntest/supplemental/204status.json b/adapters/adkernelAdn/adkerneladntest/supplemental/204status.json index ae71ff41352..3efbaf1d422 100644 --- a/adapters/adkernelAdn/adkerneladntest/supplemental/204status.json +++ b/adapters/adkernelAdn/adkerneladntest/supplemental/204status.json @@ -41,7 +41,8 @@ "site": { "page": "http://example.com/test.html" } - } + }, + "impIDs":["adunit-1"] }, "mockResponse": { "status": 204, diff --git a/adapters/adkernelAdn/adkerneladntest/supplemental/http-err-status.json b/adapters/adkernelAdn/adkerneladntest/supplemental/http-err-status.json index 8a25aae0ecd..6ed9013e102 100644 --- a/adapters/adkernelAdn/adkerneladntest/supplemental/http-err-status.json +++ b/adapters/adkernelAdn/adkerneladntest/supplemental/http-err-status.json @@ -41,7 +41,8 @@ "site": { "page": "http://example.com/test.html" } - } + }, + "impIDs":["adunit-1"] }, "mockResponse": { "status": 404 diff --git a/adapters/adkernelAdn/adkerneladntest/supplemental/two-impressions-two-seatbids.json b/adapters/adkernelAdn/adkerneladntest/supplemental/two-impressions-two-seatbids.json index 37a5a36d859..e538e33a097 100644 --- a/adapters/adkernelAdn/adkerneladntest/supplemental/two-impressions-two-seatbids.json +++ b/adapters/adkernelAdn/adkerneladntest/supplemental/two-impressions-two-seatbids.json @@ -56,7 +56,8 @@ "video": {"mimes": ["video/mp4"]} } ] - } + }, + "impIDs":["banner-adunit-1","video-adunit-2"] }, "mockResponse": { "status": 200, diff --git a/adapters/adkernelAdn/adkerneladntest/supplemental/wrong-imp-ext-1.json b/adapters/adkernelAdn/adkerneladntest/supplemental/wrong-imp-ext-1.json index 8b2b5837e13..4d5b3228d16 100644 --- a/adapters/adkernelAdn/adkerneladntest/supplemental/wrong-imp-ext-1.json +++ b/adapters/adkernelAdn/adkerneladntest/supplemental/wrong-imp-ext-1.json @@ -18,8 +18,8 @@ "expectedMakeRequestsErrors": [ { - "value": "json: cannot unmarshal string into Go struct field ExtImpAdkernelAdn.pubId of type int", - "comparison": "literal" + "value": "cannot unmarshal openrtb_ext.ExtImpAdkernelAdn.PublisherID: unexpected character", + "comparison": "startswith" } ] } diff --git a/adapters/adman/adman.go b/adapters/adman/adman.go index 5350fa7cb86..8f90a0fe5bb 100644 --- a/adapters/adman/adman.go +++ b/adapters/adman/adman.go @@ -5,11 +5,12 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) // AdmanAdapter struct @@ -38,12 +39,12 @@ func (a *AdmanAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapt reqCopy.Imp = []openrtb2.Imp{imp} var bidderExt adapters.ExtImpBidder - if err = json.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt); err != nil { + if err = jsonutil.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt); err != nil { errs = append(errs, err) continue } - if err = json.Unmarshal(bidderExt.Bidder, &admanExt); err != nil { + if err = jsonutil.Unmarshal(bidderExt.Bidder, &admanExt); err != nil { errs = append(errs, err) continue } @@ -78,6 +79,7 @@ func (a *AdmanAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.Requ Uri: a.URI, Body: reqJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }, errs } @@ -97,7 +99,7 @@ func (a *AdmanAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRe var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } diff --git a/adapters/adman/adman_test.go b/adapters/adman/adman_test.go index 608232cc4b8..1a8eccca62b 100644 --- a/adapters/adman/adman_test.go +++ b/adapters/adman/adman_test.go @@ -3,9 +3,9 @@ package adman import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adman/admantest/exemplary/simple-banner.json b/adapters/adman/admantest/exemplary/simple-banner.json index 8bbe16aa0fe..e6abaf6a1a4 100644 --- a/adapters/adman/admantest/exemplary/simple-banner.json +++ b/adapters/adman/admantest/exemplary/simple-banner.json @@ -71,7 +71,8 @@ "ip": "123.123.123.123", "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adman/admantest/exemplary/simple-video.json b/adapters/adman/admantest/exemplary/simple-video.json index 159a30a93e0..ef227371957 100644 --- a/adapters/adman/admantest/exemplary/simple-video.json +++ b/adapters/adman/admantest/exemplary/simple-video.json @@ -58,7 +58,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adman/admantest/exemplary/simple-web-banner.json b/adapters/adman/admantest/exemplary/simple-web-banner.json index 0ceaac7c6d5..6dcf5f05af1 100644 --- a/adapters/adman/admantest/exemplary/simple-web-banner.json +++ b/adapters/adman/admantest/exemplary/simple-web-banner.json @@ -69,7 +69,8 @@ "device": { "ip": "123.123.123.123" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adman/admantest/supplemental/bad-imp-ext.json b/adapters/adman/admantest/supplemental/bad-imp-ext.json index db3c8de5767..a4b5cc546f4 100644 --- a/adapters/adman/admantest/supplemental/bad-imp-ext.json +++ b/adapters/adman/admantest/supplemental/bad-imp-ext.json @@ -35,8 +35,8 @@ }, "expectedMakeRequestsErrors": [ { - "value": "unexpected end of JSON input", - "comparison": "literal" + "value": "expect { or n, but found", + "comparison": "startswith" } ] } diff --git a/adapters/adman/admantest/supplemental/bad_response.json b/adapters/adman/admantest/supplemental/bad_response.json index 4431a328154..96d7a6098ca 100644 --- a/adapters/adman/admantest/supplemental/bad_response.json +++ b/adapters/adman/admantest/supplemental/bad_response.json @@ -69,7 +69,8 @@ "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -78,7 +79,7 @@ }], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/adman/admantest/supplemental/no-imp-ext-1.json b/adapters/adman/admantest/supplemental/no-imp-ext-1.json index 8fad5ba5ef0..d93995bb33e 100644 --- a/adapters/adman/admantest/supplemental/no-imp-ext-1.json +++ b/adapters/adman/admantest/supplemental/no-imp-ext-1.json @@ -31,7 +31,7 @@ }, "expectedMakeRequestsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/adman/admantest/supplemental/no-imp-ext-2.json b/adapters/adman/admantest/supplemental/no-imp-ext-2.json index 337dfd044b3..677260f21e4 100644 --- a/adapters/adman/admantest/supplemental/no-imp-ext-2.json +++ b/adapters/adman/admantest/supplemental/no-imp-ext-2.json @@ -31,8 +31,8 @@ }, "expectedMakeRequestsErrors": [ { - "value": "unexpected end of JSON input", - "comparison": "literal" + "value": "expect { or n, but found", + "comparison": "startswith" } ] } diff --git a/adapters/adman/admantest/supplemental/status-204.json b/adapters/adman/admantest/supplemental/status-204.json index 05f9d030832..859524562d0 100644 --- a/adapters/adman/admantest/supplemental/status-204.json +++ b/adapters/adman/admantest/supplemental/status-204.json @@ -70,7 +70,8 @@ "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/adman/admantest/supplemental/status-404.json b/adapters/adman/admantest/supplemental/status-404.json index 043afbdc1dc..8896b1d0ed6 100644 --- a/adapters/adman/admantest/supplemental/status-404.json +++ b/adapters/adman/admantest/supplemental/status-404.json @@ -69,7 +69,8 @@ "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 404, diff --git a/adapters/adman/params_test.go b/adapters/adman/params_test.go index a80c2a44b8b..97b3d8f3f2c 100644 --- a/adapters/adman/params_test.go +++ b/adapters/adman/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // TestValidParams makes sure that the adman schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/admatic/admatic.go b/adapters/admatic/admatic.go new file mode 100644 index 00000000000..6b0ed99f4df --- /dev/null +++ b/adapters/admatic/admatic.go @@ -0,0 +1,139 @@ +package admatic + +import ( + "encoding/json" + "fmt" + "net/http" + "text/template" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +type adapter struct { + endpoint *template.Template +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + endpointTemplate, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint template: %v", err) + } + + bidder := &adapter{ + endpoint: endpointTemplate, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var requests []*adapters.RequestData + var errs []error + + requestCopy := *request + for _, imp := range request.Imp { + requestCopy.Imp = []openrtb2.Imp{imp} + + endpoint, err := a.buildEndpointFromRequest(&imp) + if err != nil { + errs = append(errs, err) + continue + } + + requestJSON, err := json.Marshal(requestCopy) + if err != nil { + errs = append(errs, err) + continue + } + + request := &adapters.RequestData{ + Method: http.MethodPost, + Body: requestJSON, + Uri: endpoint, + ImpIDs: openrtb_ext.GetImpIDs(requestCopy.Imp), + } + + requests = append(requests, request) + } + + return requests, errs +} + +func (a *adapter) buildEndpointFromRequest(imp *openrtb2.Imp) (string, error) { + var impExt adapters.ExtImpBidder + if err := jsonutil.Unmarshal(imp.Ext, &impExt); err != nil { + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to deserialize bidder impression extension: %v", err), + } + } + + var admaticExt openrtb_ext.ImpExtAdmatic + if err := jsonutil.Unmarshal(impExt.Bidder, &admaticExt); err != nil { + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to deserialize AdMatic extension: %v", err), + } + } + + endpointParams := macros.EndpointTemplateParams{ + Host: admaticExt.Host, + } + + return macros.ResolveMacros(a.endpoint, endpointParams) +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + err := adapters.CheckResponseStatusCodeForErrors(responseData) + if err != nil { + return nil, []error{err} + } + var response openrtb2.BidResponse + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + if len(response.Cur) != 0 { + bidResponse.Currency = response.Cur + } + + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + + bidMediaType, err := getMediaTypeForBid(seatBid.Bid[i].ImpID, request.Imp) + if err != nil { + return nil, []error{err} + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidMediaType, + }) + } + } + return bidResponse, nil +} + +func getMediaTypeForBid(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { + for _, imp := range imps { + if imp.ID == impID { + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } else if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } else if imp.Native != nil { + return openrtb_ext.BidTypeNative, nil + } + } + } + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("The impression with ID %s is not present into the request", impID), + } +} diff --git a/adapters/admatic/admatic_test.go b/adapters/admatic/admatic_test.go new file mode 100644 index 00000000000..c98f14e90a0 --- /dev/null +++ b/adapters/admatic/admatic_test.go @@ -0,0 +1,29 @@ +package admatic + +import ( + "testing" + + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderAdmatic, config.Adapter{ + Endpoint: "http://pbs.admatic.com.tr?host={{.Host}}"}, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1281, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "admatictest", bidder) +} + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderAdmatic, config.Adapter{ + Endpoint: "host={{Host}}"}, config.Server{ExternalUrl: "http://hosturl.com"}) + + assert.Error(t, buildErr) +} diff --git a/adapters/admatic/admatictest/exemplary/banner.json b/adapters/admatic/admatictest/exemplary/banner.json new file mode 100644 index 00000000000..5950034a61e --- /dev/null +++ b/adapters/admatic/admatictest/exemplary/banner.json @@ -0,0 +1,96 @@ +{ + "mockBidRequest": { + "id": "test-request-id-banner", + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.admatic.com.tr?host=layer.serve.admatic.com.tr", + "body": { + "id": "test-request-id-banner", + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345 + } + } + } + ] + }, + "impIDs": ["test-imp-id-banner"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-banner", + "seatbid": [ + { + "seat": "admatic", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/admatic/admatictest/exemplary/multiple-imps.json b/adapters/admatic/admatictest/exemplary/multiple-imps.json new file mode 100644 index 00000000000..8738fbb7f95 --- /dev/null +++ b/adapters/admatic/admatictest/exemplary/multiple-imps.json @@ -0,0 +1,181 @@ +{ + "mockBidRequest": { + "id": "test-request-id-banner", + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345 + } + } + }, + { + "id": "test-imp-id-banner2", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "host": "layer2.serve.admatic.com.tr", + "networkId": 123456 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.admatic.com.tr?host=layer.serve.admatic.com.tr", + "body": { + "id": "test-request-id-banner", + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345 + } + } + } + ] + }, + "impIDs": ["test-imp-id-banner"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-banner", + "seatbid": [ + { + "seat": "admatic", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + } + ] + } + ], + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "uri": "http://pbs.admatic.com.tr?host=layer2.serve.admatic.com.tr", + "body": { + "id": "test-request-id-banner", + "imp": [ + { + "id": "test-imp-id-banner2", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "host": "layer2.serve.admatic.com.tr", + "networkId": 123456 + } + } + } + ] + }, + "impIDs": ["test-imp-id-banner2"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-banner", + "seatbid": [ + { + "seat": "admatic", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e801", + "impid": "test-imp-id-banner2", + "price": 0.5, + "adm": "some-test-ad-banner2", + "crid": "crid_11", + "w": 728, + "h": 90 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e801", + "impid": "test-imp-id-banner2", + "price": 0.5, + "adm": "some-test-ad-banner2", + "crid": "crid_11", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/admatic/admatictest/exemplary/native.json b/adapters/admatic/admatictest/exemplary/native.json new file mode 100644 index 00000000000..455406d05c2 --- /dev/null +++ b/adapters/admatic/admatictest/exemplary/native.json @@ -0,0 +1,86 @@ +{ + "mockBidRequest": { + "id": "test-request-id-native", + "imp": [ + { + "id": "test-imp-id-native", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.admatic.com.tr?host=layer.serve.admatic.com.tr", + "body": { + "id": "test-request-id-native", + "imp": [ + { + "id": "test-imp-id-native", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345 + } + } + } + ] + }, + "impIDs": ["test-imp-id-native"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-native", + "seatbid": [ + { + "seat": "admatic", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-native", + "price": 0.5, + "adm": "some-test-ad-native", + "crid": "crid_10", + "w": 728, + "h": 90 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-native", + "price": 0.5, + "adm": "some-test-ad-native", + "crid": "crid_10", + "w": 728, + "h": 90 + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/admatic/admatictest/exemplary/optional-params.json b/adapters/admatic/admatictest/exemplary/optional-params.json new file mode 100644 index 00000000000..e1652c30899 --- /dev/null +++ b/adapters/admatic/admatictest/exemplary/optional-params.json @@ -0,0 +1,100 @@ +{ + "mockBidRequest": { + "id": "test-request-id-banner", + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345, + "bidFloor": 0.1, + "isTest": false + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.admatic.com.tr?host=layer.serve.admatic.com.tr", + "body": { + "id": "test-request-id-banner", + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345, + "bidFloor": 0.1, + "isTest": false + } + } + } + ] + }, + "impIDs": ["test-imp-id-banner"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-banner", + "seatbid": [ + { + "seat": "admatic", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/admatic/admatictest/exemplary/video.json b/adapters/admatic/admatictest/exemplary/video.json new file mode 100644 index 00000000000..58f5e89810b --- /dev/null +++ b/adapters/admatic/admatictest/exemplary/video.json @@ -0,0 +1,94 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-video-id", + "video": { + "mimes": ["video/mp4"], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.admatic.com.tr?host=layer.serve.admatic.com.tr", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-video-id", + "video": { + "mimes": ["video/mp4"], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345 + } + } + } + ] + }, + "impIDs": ["test-video-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-test-video-id", + "seatbid": [ + { + "seat": "test-seat", + "bid": [ + { + "id": "5dce6055-a93c-1fd0-8c29-14afc3e510fd", + "impid": "test-video-id", + "price": 0.1529, + "nurl": "test-win", + "adm": "test-video", + "adid": "92-288", + "adomain": ["advertiserdomain.com"], + "crid": "288", + "w": 300, + "h": 250 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "5dce6055-a93c-1fd0-8c29-14afc3e510fd", + "impid": "test-video-id", + "price": 0.1529, + "nurl": "test-win", + "adm": "test-video", + "adid": "92-288", + "adomain": ["advertiserdomain.com"], + "crid": "288", + "w": 300, + "h": 250 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/admatic/admatictest/supplemental/bad-request.json b/adapters/admatic/admatictest/supplemental/bad-request.json new file mode 100644 index 00000000000..90f6ebd6c9d --- /dev/null +++ b/adapters/admatic/admatictest/supplemental/bad-request.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.admatic.com.tr?host=layer.serve.admatic.com.tr", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345 + } + } + } + ] + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 400, + "headers": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] + } + \ No newline at end of file diff --git a/adapters/admatic/admatictest/supplemental/multiple-imps-with-error.json b/adapters/admatic/admatictest/supplemental/multiple-imps-with-error.json new file mode 100644 index 00000000000..b23c794c1ba --- /dev/null +++ b/adapters/admatic/admatictest/supplemental/multiple-imps-with-error.json @@ -0,0 +1,119 @@ +{ + "mockBidRequest": { + "id": "test-request-id-banner", + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345 + } + } + }, + { + "id": "test-imp-id-banner2", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "host": {}, + "networkId": 12345 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.admatic.com.tr?host=layer.serve.admatic.com.tr", + "body": { + "id": "test-request-id-banner", + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345 + } + } + } + ] + }, + "impIDs": ["test-imp-id-banner"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id-banner", + "seatbid": [ + { + "seat": "admatic", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeRequestsErrors": [ + { + "value": "Failed to deserialize AdMatic extension: cannot unmarshal openrtb_ext.ImpExtAdmatic.Host: expects \" or n, but found {", + "comparison": "literal" + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-banner", + "crid": "crid_10", + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/admatic/admatictest/supplemental/response-200-without-body.json b/adapters/admatic/admatictest/supplemental/response-200-without-body.json new file mode 100644 index 00000000000..6cded7606e2 --- /dev/null +++ b/adapters/admatic/admatictest/supplemental/response-200-without-body.json @@ -0,0 +1,64 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.admatic.com.tr?host=layer.serve.admatic.com.tr", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345 + } + } + } + ] + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "expect { or n, but found", + "comparison": "startswith" + } + ] +} \ No newline at end of file diff --git a/adapters/admatic/admatictest/supplemental/response-204.json b/adapters/admatic/admatictest/supplemental/response-204.json new file mode 100644 index 00000000000..849cc85e89b --- /dev/null +++ b/adapters/admatic/admatictest/supplemental/response-204.json @@ -0,0 +1,59 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.admatic.com.tr?host=layer.serve.admatic.com.tr", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345 + } + } + } + ] + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/admatic/admatictest/supplemental/server-error.json b/adapters/admatic/admatictest/supplemental/server-error.json new file mode 100644 index 00000000000..91bb22733bc --- /dev/null +++ b/adapters/admatic/admatictest/supplemental/server-error.json @@ -0,0 +1,65 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://pbs.admatic.com.tr?host=layer.serve.admatic.com.tr", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "host": "layer.serve.admatic.com.tr", + "networkId": 12345 + } + } + } + ] + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 500, + "headers": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/admatic/params_test.go b/adapters/admatic/params_test.go new file mode 100644 index 00000000000..fc92a1d7799 --- /dev/null +++ b/adapters/admatic/params_test.go @@ -0,0 +1,56 @@ +package admatic + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderAdmatic, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderAdmatic, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{ "host": "layer.serve.admatic.com.tr", + "networkId": 1111, + "ext": { + "key1": "value1", + "key2": "value2" + } + }`, + `{"host": "layer.serve.admatic.com.tr", "networkId": 1111}`, +} + +var invalidParams = []string{ + `{"ext": { + "key1": "value1", + "key2": "value2" + }`, + `{}`, + `{"host": 123, "networkId":"1111"}`, + `{"host": "layer.serve.admatic.com.tr", "networkId":"1111"}`, + `{"host": 1111, "networkId":1111}`, +} diff --git a/adapters/admixer/admixer.go b/adapters/admixer/admixer.go index 9a07a8922a9..726c2cb98cc 100644 --- a/adapters/admixer/admixer.go +++ b/adapters/admixer/admixer.go @@ -5,11 +5,12 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type AdmixerAdapter struct { @@ -81,19 +82,20 @@ func (a *AdmixerAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.Re Uri: a.endpoint, Body: reqJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }, errs } func preprocess(imp *openrtb2.Imp) error { var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { return &errortypes.BadInput{ Message: err.Error(), } } var admixerExt openrtb_ext.ExtImpAdmixer - if err := json.Unmarshal(bidderExt.Bidder, &admixerExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &admixerExt); err != nil { return &errortypes.BadInput{ Message: "Wrong Admixer bidder ext", } @@ -153,7 +155,7 @@ func (a *AdmixerAdapter) MakeBids(internalRequest *openrtb2.BidRequest, external } var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } diff --git a/adapters/admixer/admixer_test.go b/adapters/admixer/admixer_test.go index 766f890cdf7..62615a63b3c 100644 --- a/adapters/admixer/admixer_test.go +++ b/adapters/admixer/admixer_test.go @@ -3,9 +3,9 @@ package admixer import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/admixer/admixertest/exemplary/optional-params.json b/adapters/admixer/admixertest/exemplary/optional-params.json index b93aa9c8154..6d60878c835 100644 --- a/adapters/admixer/admixertest/exemplary/optional-params.json +++ b/adapters/admixer/admixertest/exemplary/optional-params.json @@ -227,7 +227,8 @@ } } ] - } + }, + "impIDs":["test-imp-id","test-imp-id","test-imp-id","test-imp-id","test-imp-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/admixer/admixertest/exemplary/simple-app-audio.json b/adapters/admixer/admixertest/exemplary/simple-app-audio.json index b8c39ead95e..3b6673180a4 100644 --- a/adapters/admixer/admixertest/exemplary/simple-app-audio.json +++ b/adapters/admixer/admixertest/exemplary/simple-app-audio.json @@ -45,7 +45,8 @@ "tagid": "473e443c-43d0-423d-a8d7-a302637a01d8" } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/admixer/admixertest/exemplary/simple-app-banner.json b/adapters/admixer/admixertest/exemplary/simple-app-banner.json index aff4ccddd64..1be2f132bdf 100644 --- a/adapters/admixer/admixertest/exemplary/simple-app-banner.json +++ b/adapters/admixer/admixertest/exemplary/simple-app-banner.json @@ -52,7 +52,8 @@ "tagid": "2eb6bd58-865c-47ce-af7f-a918108c3fd2" } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/admixer/admixertest/exemplary/simple-app-native.json b/adapters/admixer/admixertest/exemplary/simple-app-native.json index 38c005c651c..1a99f2a4488 100644 --- a/adapters/admixer/admixertest/exemplary/simple-app-native.json +++ b/adapters/admixer/admixertest/exemplary/simple-app-native.json @@ -46,7 +46,8 @@ "tagid": "b1fbebfc-7155-4922-bb86-615e7f3d6eef" } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/admixer/admixertest/exemplary/simple-app-video.json b/adapters/admixer/admixertest/exemplary/simple-app-video.json index 627023fa1e6..a94d1faf422 100644 --- a/adapters/admixer/admixertest/exemplary/simple-app-video.json +++ b/adapters/admixer/admixertest/exemplary/simple-app-video.json @@ -62,7 +62,8 @@ "tagid": "ac7fa772-d7be-48cc-820b-e21728e434fe" } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/admixer/admixertest/exemplary/simple-site-audio.json b/adapters/admixer/admixertest/exemplary/simple-site-audio.json index 5a1d6531a85..c141442e7a5 100644 --- a/adapters/admixer/admixertest/exemplary/simple-site-audio.json +++ b/adapters/admixer/admixertest/exemplary/simple-site-audio.json @@ -45,7 +45,8 @@ "tagid": "473e443c-43d0-423d-a8d7-a302637a01d8" } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/admixer/admixertest/exemplary/simple-site-banner.json b/adapters/admixer/admixertest/exemplary/simple-site-banner.json index bd50aba8d1a..776cdcff50e 100644 --- a/adapters/admixer/admixertest/exemplary/simple-site-banner.json +++ b/adapters/admixer/admixertest/exemplary/simple-site-banner.json @@ -52,7 +52,8 @@ "tagid": "2eb6bd58-865c-47ce-af7f-a918108c3fd2" } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/admixer/admixertest/exemplary/simple-site-native.json b/adapters/admixer/admixertest/exemplary/simple-site-native.json index 246d02025b1..95f604ce2d9 100644 --- a/adapters/admixer/admixertest/exemplary/simple-site-native.json +++ b/adapters/admixer/admixertest/exemplary/simple-site-native.json @@ -46,7 +46,8 @@ "tagid": "b1fbebfc-7155-4922-bb86-615e7f3d6eef" } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/admixer/admixertest/exemplary/simple-site-video.json b/adapters/admixer/admixertest/exemplary/simple-site-video.json index 42d771ce86b..5f3a0e1037c 100644 --- a/adapters/admixer/admixertest/exemplary/simple-site-video.json +++ b/adapters/admixer/admixertest/exemplary/simple-site-video.json @@ -62,7 +62,8 @@ "tagid": "ac7fa772-d7be-48cc-820b-e21728e434fe" } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/admixer/admixertest/supplemental/bad-dsp-request-example.json b/adapters/admixer/admixertest/supplemental/bad-dsp-request-example.json index 5256c14050b..1e10f331c8c 100644 --- a/adapters/admixer/admixertest/supplemental/bad-dsp-request-example.json +++ b/adapters/admixer/admixertest/supplemental/bad-dsp-request-example.json @@ -52,7 +52,8 @@ "tagid": "3e56bd58-865c-47ce-af7f-a918108c3fd2" } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/admixer/admixertest/supplemental/dsp-server-internal-error-example.json b/adapters/admixer/admixertest/supplemental/dsp-server-internal-error-example.json index 1c06eadce44..aa3f0f17dd5 100644 --- a/adapters/admixer/admixertest/supplemental/dsp-server-internal-error-example.json +++ b/adapters/admixer/admixertest/supplemental/dsp-server-internal-error-example.json @@ -52,7 +52,8 @@ "tagid": "3e56bd58-865c-47ce-af7f-a918108c3fd2" } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 500, diff --git a/adapters/admixer/admixertest/supplemental/unknown-status-code-example.json b/adapters/admixer/admixertest/supplemental/unknown-status-code-example.json index 972f2f5dd01..aeebb1e0b7d 100644 --- a/adapters/admixer/admixertest/supplemental/unknown-status-code-example.json +++ b/adapters/admixer/admixertest/supplemental/unknown-status-code-example.json @@ -52,7 +52,8 @@ "tagid": "3e56bd58-865c-47ce-af7f-a918108c3fd2" } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 301, diff --git a/adapters/admixer/params_test.go b/adapters/admixer/params_test.go index bfa75a4884f..cdc8d56c77a 100644 --- a/adapters/admixer/params_test.go +++ b/adapters/admixer/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // This file actually intends to test static/bidder-params/admixer.json diff --git a/adapters/adnuntius/adnuntius.go b/adapters/adnuntius/adnuntius.go index fd667ddc506..a3024c38d1b 100644 --- a/adapters/adnuntius/adnuntius.go +++ b/adapters/adnuntius/adnuntius.go @@ -9,12 +9,13 @@ import ( "strings" "github.com/buger/jsonparser" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/timeutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/timeutil" ) type QueryString map[string]string @@ -33,6 +34,14 @@ type adnAdunit struct { type extDeviceAdnuntius struct { NoCookies bool `json:"noCookies,omitempty"` } +type siteExt struct { + Data interface{} `json:"data"` +} + +type adnAdvertiser struct { + LegalName string `json:"legalName,omitempty"` + Name string `json:"name,omitempty"` +} type Ad struct { Bid struct { @@ -53,6 +62,7 @@ type Ad struct { LineItemId string Html string DestinationUrls map[string]string + Advertiser adnAdvertiser `json:"advertiser,omitempty"` } type AdUnit struct { @@ -71,9 +81,10 @@ type adnMetaData struct { Usi string `json:"usi,omitempty"` } type adnRequest struct { - AdUnits []adnAdunit `json:"adUnits"` - MetaData adnMetaData `json:"metaData,omitempty"` - Context string `json:"context,omitempty"` + AdUnits []adnAdunit `json:"adUnits"` + MetaData adnMetaData `json:"metaData,omitempty"` + Context string `json:"context,omitempty"` + KeyValues interface{} `json:"kv,omitempty"` } type RequestExt struct { @@ -130,7 +141,7 @@ func makeEndpointUrl(ortbRequest openrtb2.BidRequest, a *adapter, noCookies bool if !noCookies { var deviceExt extDeviceAdnuntius if ortbRequest.Device != nil && ortbRequest.Device.Ext != nil { - if err := json.Unmarshal(ortbRequest.Device.Ext, &deviceExt); err != nil { + if err := jsonutil.Unmarshal(ortbRequest.Device.Ext, &deviceExt); err != nil { return "", []error{fmt.Errorf("failed to parse Adnuntius endpoint: %v", err)} } } @@ -138,7 +149,6 @@ func makeEndpointUrl(ortbRequest openrtb2.BidRequest, a *adapter, noCookies bool if deviceExt.NoCookies { noCookies = true } - } _, offset := a.time.Now().Zone() @@ -159,7 +169,7 @@ func makeEndpointUrl(ortbRequest openrtb2.BidRequest, a *adapter, noCookies bool } q.Set("tzo", fmt.Sprint(tzo)) - q.Set("format", "json") + q.Set("format", "prebid") url := endpointUrl + "?" + q.Encode() return url, nil @@ -202,20 +212,20 @@ func (a *adapter) generateRequests(ortbRequest openrtb2.BidRequest) ([]*adapters } var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, []error{&errortypes.BadInput{ Message: fmt.Sprintf("Error unmarshalling ExtImpBidder: %s", err.Error()), }} } var adnuntiusExt openrtb_ext.ImpExtAdnunitus - if err := json.Unmarshal(bidderExt.Bidder, &adnuntiusExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &adnuntiusExt); err != nil { return nil, []error{&errortypes.BadInput{ Message: fmt.Sprintf("Error unmarshalling ExtImpValues: %s", err.Error()), }} } - if adnuntiusExt.NoCookies == true { + if adnuntiusExt.NoCookies { noCookies = true } @@ -249,11 +259,31 @@ func (a *adapter) generateRequests(ortbRequest openrtb2.BidRequest) ([]*adapters site = ortbRequest.Site.Page } + extSite, erro := getSiteExtAsKv(&ortbRequest) + if erro != nil { + return nil, []error{fmt.Errorf("failed to parse site Ext: %v", err)} + } + for _, networkAdunits := range networkAdunitMap { adnuntiusRequest := adnRequest{ - AdUnits: networkAdunits, - Context: site, + AdUnits: networkAdunits, + Context: site, + KeyValues: extSite.Data, + } + + var extUser openrtb_ext.ExtUser + if ortbRequest.User != nil && ortbRequest.User.Ext != nil { + if err := jsonutil.Unmarshal(ortbRequest.User.Ext, &extUser); err != nil { + return nil, []error{fmt.Errorf("failed to parse Ext User: %v", err)} + } + } + + // Will change when our adserver can accept multiple user IDS + if extUser.Eids != nil && len(extUser.Eids) > 0 { + if len(extUser.Eids[0].UIDs) > 0 { + adnuntiusRequest.MetaData.Usi = extUser.Eids[0].UIDs[0].ID + } } ortbUser := ortbRequest.User @@ -276,6 +306,7 @@ func (a *adapter) generateRequests(ortbRequest openrtb2.BidRequest) ([]*adapters Uri: endpoint, Body: adnJson, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(ortbRequest.Imp), }) } @@ -298,7 +329,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, externalRequest *adapte } var adnResponse AdnResponse - if err := json.Unmarshal(response.Body, &adnResponse); err != nil { + if err := jsonutil.Unmarshal(response.Body, &adnResponse); err != nil { return nil, []error{err} } @@ -310,12 +341,22 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, externalRequest *adapte return bidResponse, nil } +func getSiteExtAsKv(request *openrtb2.BidRequest) (siteExt, error) { + var extSite siteExt + if request.Site != nil && request.Site.Ext != nil { + if err := jsonutil.Unmarshal(request.Site.Ext, &extSite); err != nil { + return extSite, fmt.Errorf("failed to parse ExtSite in Adnuntius: %v", err) + } + } + return extSite, nil +} + func getGDPR(request *openrtb2.BidRequest) (string, string, error) { gdpr := "" var extRegs openrtb_ext.ExtRegs if request.Regs != nil && request.Regs.Ext != nil { - if err := json.Unmarshal(request.Regs.Ext, &extRegs); err != nil { + if err := jsonutil.Unmarshal(request.Regs.Ext, &extRegs); err != nil { return "", "", fmt.Errorf("failed to parse ExtRegs in Adnuntius GDPR check: %v", err) } if extRegs.GDPR != nil && (*extRegs.GDPR == 0 || *extRegs.GDPR == 1) { @@ -326,7 +367,7 @@ func getGDPR(request *openrtb2.BidRequest) (string, string, error) { consent := "" if request.User != nil && request.User.Ext != nil { var extUser openrtb_ext.ExtUser - if err := json.Unmarshal(request.User.Ext, &extUser); err != nil { + if err := jsonutil.Unmarshal(request.User.Ext, &extUser); err != nil { return "", "", fmt.Errorf("failed to parse ExtUser in Adnuntius GDPR check: %v", err) } consent = extUser.Consent @@ -335,6 +376,40 @@ func getGDPR(request *openrtb2.BidRequest) (string, string, error) { return gdpr, consent, nil } +func generateReturnExt(ad Ad, request *openrtb2.BidRequest) (json.RawMessage, error) { + // We always force the publisher to render + var adRender int8 = 0 + + var requestRegsExt *openrtb_ext.ExtRegs + if request.Regs != nil && request.Regs.Ext != nil { + if err := jsonutil.Unmarshal(request.Regs.Ext, &requestRegsExt); err != nil { + + return nil, fmt.Errorf("Failed to parse Ext information in Adnuntius: %v", err) + } + } + + if ad.Advertiser.Name != "" && requestRegsExt != nil && requestRegsExt.DSA != nil { + legalName := ad.Advertiser.Name + if ad.Advertiser.LegalName != "" { + legalName = ad.Advertiser.LegalName + } + ext := &openrtb_ext.ExtBid{ + DSA: &openrtb_ext.ExtBidDSA{ + AdRender: &adRender, + Paid: legalName, + Behalf: legalName, + }, + } + returnExt, err := json.Marshal(ext) + if err != nil { + return nil, fmt.Errorf("Failed to parse Ext information in Adnuntius: %v", err) + } + + return returnExt, nil + } + return nil, nil +} + func generateAdResponse(ad Ad, imp openrtb2.Imp, html string, request *openrtb2.BidRequest) (*openrtb2.Bid, []error) { creativeWidth, widthErr := strconv.ParseInt(ad.CreativeWidth, 10, 64) @@ -354,14 +429,14 @@ func generateAdResponse(ad Ad, imp openrtb2.Imp, html string, request *openrtb2. price := ad.Bid.Amount var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, []error{&errortypes.BadInput{ Message: fmt.Sprintf("Error unmarshalling ExtImpBidder: %s", err.Error()), }} } var adnuntiusExt openrtb_ext.ImpExtAdnunitus - if err := json.Unmarshal(bidderExt.Bidder, &adnuntiusExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &adnuntiusExt); err != nil { return nil, []error{&errortypes.BadInput{ Message: fmt.Sprintf("Error unmarshalling ExtImpValues: %s", err.Error()), }} @@ -376,6 +451,13 @@ func generateAdResponse(ad Ad, imp openrtb2.Imp, html string, request *openrtb2. } } + extJson, err := generateReturnExt(ad, request) + if err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Error extracting Ext: %s", err.Error()), + }} + } + adDomain := []string{} for _, url := range ad.DestinationUrls { domainArray := strings.Split(url, "/") @@ -395,6 +477,7 @@ func generateAdResponse(ad Ad, imp openrtb2.Imp, html string, request *openrtb2. Price: price * 1000, AdM: html, ADomain: adDomain, + Ext: extJson, } return &bid, nil @@ -429,7 +512,7 @@ func generateBidResponse(adnResponse *AdnResponse, request *openrtb2.BidRequest) adBid, err := generateAdResponse(ad, imp, adunit.Html, request) if err != nil { return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("Error at ad generation"), + Message: "Error at ad generation", }} } @@ -442,7 +525,7 @@ func generateBidResponse(adnResponse *AdnResponse, request *openrtb2.BidRequest) dealBid, err := generateAdResponse(deal, imp, deal.Html, request) if err != nil { return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("Error at ad generation"), + Message: "Error at ad generation", }} } diff --git a/adapters/adnuntius/adnuntius_test.go b/adapters/adnuntius/adnuntius_test.go index 9c431c2a315..8b048f7996b 100644 --- a/adapters/adnuntius/adnuntius_test.go +++ b/adapters/adnuntius/adnuntius_test.go @@ -4,10 +4,10 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adnuntius/adnuntiustest/exemplary/simple-banner.json b/adapters/adnuntius/adnuntiustest/exemplary/simple-banner.json index 3a50789e4dd..49c74ad6683 100644 --- a/adapters/adnuntius/adnuntiustest/exemplary/simple-banner.json +++ b/adapters/adnuntius/adnuntiustest/exemplary/simple-banner.json @@ -31,7 +31,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://whatever.url?format=json&tzo=0", + "uri": "http://whatever.url?format=prebid&tzo=0", "body": { "adUnits": [ { @@ -42,7 +42,8 @@ ], "context": "prebid.org", "metaData": {} - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-dealId.json b/adapters/adnuntius/adnuntiustest/supplemental/check-dealId.json index 2565fee93c9..7869fa87f94 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/check-dealId.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-dealId.json @@ -31,7 +31,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://whatever.url?format=json&tzo=0", + "uri": "http://whatever.url?format=prebid&tzo=0", "body": { "adUnits": [ { @@ -42,7 +42,8 @@ ], "context": "prebid.org", "metaData": {} - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-legalName-omitted.json b/adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-legalName-omitted.json new file mode 100644 index 00000000000..0b44aa16dca --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-legalName-omitted.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl" + }, + "site": { + "ext":{ + "data" : { + "key": ["value"] + } + } + }, + "regs": { + "ext": { + "dsa": { + "dsarequired": 3, + "datatopub": 1 + } + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=prebid&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id", + "dimensions": [[300,250],[300,600]] + } + ], + "kv": { + "key": ["value"] + }, + "metaData": { + "usi": "1kjh3429kjh295jkl" + }, + "context": "unknown" + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { + "amount": 20.0, + "currency": "NOK" + }, + "adId": "adn-id-1559784094", + "creativeWidth": "980", + "creativeHeight": "240", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv", + "advertiser": { + "name": "Name" + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "adn-id-1559784094", + "impid": "test-imp-id", + "price": 20000, + "adm": "", + "adid": "adn-id-1559784094", + "adomain": [ + "google.com" + ], + "cid": "q7y9qm5b0xt9htrv", + "crid": "jn9hpzvlsf8cpdmm", + "w": 980, + "h": 240, + "ext": { + "dsa": { + "paid": "Name", + "behalf": "Name", + "adrender": 0 + } + } + }, + "type": "banner" + + } + ], + "currency": "NOK" + } + ] +} \ No newline at end of file diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-legalName.json b/adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-legalName.json new file mode 100644 index 00000000000..7999bd476aa --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-legalName.json @@ -0,0 +1,134 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl" + }, + "site": { + "ext":{ + "data" : { + "key": ["value"] + } + } + }, + "regs": { + "ext": { + "dsa": { + "dsarequired": 3, + "datatopub": 1 + } + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=prebid&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id", + "dimensions": [[300,250],[300,600]] + } + ], + "kv": { + "key": ["value"] + }, + "metaData": { + "usi": "1kjh3429kjh295jkl" + }, + "context": "unknown" + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { + "amount": 20.0, + "currency": "NOK" + }, + "adId": "adn-id-1559784094", + "creativeWidth": "980", + "creativeHeight": "240", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv", + "advertiser": { + "name": "Name", + "legalName": "LegalName" + } + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "adn-id-1559784094", + "impid": "test-imp-id", + "price": 20000, + "adm": "", + "adid": "adn-id-1559784094", + "adomain": [ + "google.com" + ], + "cid": "q7y9qm5b0xt9htrv", + "crid": "jn9hpzvlsf8cpdmm", + "w": 980, + "h": 240, + "ext": { + "dsa": { + "paid": "LegalName", + "behalf": "LegalName", + "adrender": 0 + } + } + }, + "type": "banner" + + } + ], + "currency": "NOK" + } + ] +} \ No newline at end of file diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-omitted.json b/adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-omitted.json new file mode 100644 index 00000000000..11cea9bcf66 --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-omitted.json @@ -0,0 +1,123 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl" + }, + "site": { + "ext":{ + "data" : { + "key": ["value"] + } + } + }, + "regs": { + "ext": { + "dsa": { + "dsarequired": 3, + "datatopub": 1 + } + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=prebid&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id", + "dimensions": [[300,250],[300,600]] + } + ], + "kv": { + "key": ["value"] + }, + "metaData": { + "usi": "1kjh3429kjh295jkl" + }, + "context": "unknown" + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { + "amount": 20.0, + "currency": "NOK" + }, + "adId": "adn-id-1559784094", + "creativeWidth": "980", + "creativeHeight": "240", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "adn-id-1559784094", + "impid": "test-imp-id", + "price": 20000, + "adm": "", + "adid": "adn-id-1559784094", + "adomain": [ + "google.com" + ], + "cid": "q7y9qm5b0xt9htrv", + "crid": "jn9hpzvlsf8cpdmm", + "w": 980, + "h": 240 + }, + "type": "banner" + + } + ], + "currency": "NOK" + } + ] +} \ No newline at end of file diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json b/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json index c73d69bad83..296aa4894a4 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json @@ -38,7 +38,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://gdpr.url?consentString=CONSENT_STRING&format=json&gdpr=1&tzo=0", + "uri": "http://gdpr.url?consentString=CONSENT_STRING&format=prebid&gdpr=1&tzo=0", "body": { "adUnits": [ { @@ -51,7 +51,8 @@ "usi": "1kjh3429kjh295jkl" }, "context": "unknown" - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-gross-bids.json b/adapters/adnuntius/adnuntiustest/supplemental/check-gross-bids.json index d6301fe71cf..95c77e464a9 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/check-gross-bids.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-gross-bids.json @@ -31,7 +31,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://whatever.url?format=json&tzo=0", + "uri": "http://whatever.url?format=prebid&tzo=0", "body": { "adUnits": [ { @@ -44,7 +44,8 @@ "usi": "1kjh3429kjh295jkl" }, "context": "unknown" - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-net-bids.json b/adapters/adnuntius/adnuntiustest/supplemental/check-net-bids.json index ebb25b2b7ad..236d9f6d489 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/check-net-bids.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-net-bids.json @@ -31,7 +31,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://whatever.url?format=json&tzo=0", + "uri": "http://whatever.url?format=prebid&tzo=0", "body": { "adUnits": [ { @@ -44,7 +44,8 @@ "usi": "1kjh3429kjh295jkl" }, "context": "unknown" - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies-parameter.json b/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies-parameter.json index b0d74565771..7104b6b0580 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies-parameter.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies-parameter.json @@ -31,7 +31,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://whatever.url?format=json&noCookies=true&tzo=0", + "uri": "http://whatever.url?format=prebid&noCookies=true&tzo=0", "body": { "adUnits": [ { @@ -44,7 +44,8 @@ "usi": "1kjh3429kjh295jkl" }, "context": "unknown" - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies.json b/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies.json index f1ddd3f7d5a..f60f7a636a9 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies.json @@ -35,7 +35,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://whatever.url?format=json&noCookies=true&tzo=0", + "uri": "http://whatever.url?format=prebid&noCookies=true&tzo=0", "body": { "adUnits": [ { @@ -48,7 +48,8 @@ "usi": "1kjh3429kjh295jkl" }, "context": "unknown" - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-order-multi-imp.json b/adapters/adnuntius/adnuntiustest/supplemental/check-order-multi-imp.json index d6f292d8cd5..2269297d9cd 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/check-order-multi-imp.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-order-multi-imp.json @@ -47,11 +47,11 @@ } ] }, - + "httpCalls": [ { "expectedRequest": { - "uri": "http://whatever.url?format=json&tzo=0", + "uri": "http://whatever.url?format=prebid&tzo=0", "body": { "adUnits": [ { @@ -67,7 +67,8 @@ ], "context": "prebid.org", "metaData": {} - } + }, + "impIDs":["test-imp-id","test-imp-id-2"] }, "mockResponse": { "status": 200, @@ -158,6 +159,6 @@ ], "currency": "NOK" } - + ] } diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-price-type-error.json b/adapters/adnuntius/adnuntiustest/supplemental/check-price-type-error.json index 89016087a43..35129b1ee54 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/check-price-type-error.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-price-type-error.json @@ -31,7 +31,7 @@ "httpCalls": [], "expectedMakeRequestsErrors": [ { - "value": "Error unmarshalling ExtImpValues: json: cannot unmarshal number into Go struct field ImpExtAdnunitus.bidType of type string", + "value": "Error unmarshalling ExtImpValues: cannot unmarshal openrtb_ext.ImpExtAdnunitus.BidType: expects \" or n, but found 1", "comparison": "literal" } ] diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-userId.json b/adapters/adnuntius/adnuntiustest/supplemental/check-userId.json index 4b8e6de346e..2263798d3e5 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/check-userId.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-userId.json @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://whatever.url?format=json&tzo=0", + "uri": "http://whatever.url?format=prebid&tzo=0", "body": { "adUnits": [ { @@ -43,7 +43,8 @@ "usi": "1kjh3429kjh295jkl" }, "context": "unknown" - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adnuntius/adnuntiustest/supplemental/empty-regs-ext.json b/adapters/adnuntius/adnuntiustest/supplemental/empty-regs-ext.json index f3aebd99621..211416294ce 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/empty-regs-ext.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/empty-regs-ext.json @@ -33,7 +33,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://whatever.url?format=json&tzo=0", + "uri": "http://whatever.url?format=prebid&tzo=0", "body": { "adUnits": [ { @@ -46,7 +46,8 @@ "usi": "1kjh3429kjh295jkl" }, "context": "unknown" - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adnuntius/adnuntiustest/supplemental/empty-regs.json b/adapters/adnuntius/adnuntiustest/supplemental/empty-regs.json index 06593630c43..2d3d0c861d0 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/empty-regs.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/empty-regs.json @@ -32,7 +32,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://whatever.url?format=json&tzo=0", + "uri": "http://whatever.url?format=prebid&tzo=0", "body": { "adUnits": [ { @@ -45,7 +45,8 @@ "usi": "1kjh3429kjh295jkl" }, "context": "unknown" - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adnuntius/adnuntiustest/supplemental/height-error.json b/adapters/adnuntius/adnuntiustest/supplemental/height-error.json index 1987fb9d08e..4f066aa7e1f 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/height-error.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/height-error.json @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://whatever.url?format=json&tzo=0", + "uri": "http://whatever.url?format=prebid&tzo=0", "body": { "adUnits": [ { @@ -43,7 +43,8 @@ "metaData": { } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -81,7 +82,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal number into Go struct field Ad.AdUnits.Ads.CreativeHeight of type string", + "value": "cannot unmarshal adnuntius.Ad.CreativeHeight: expects \" or n, but found 2", "comparison": "literal" } ] diff --git a/adapters/adnuntius/adnuntiustest/supplemental/invalid-regs-ext.json b/adapters/adnuntius/adnuntiustest/supplemental/invalid-regs-ext.json new file mode 100644 index 00000000000..27e043de1cb --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/invalid-regs-ext.json @@ -0,0 +1,46 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl" + }, + "site": { + "ext":{ + "data" : { + "key": ["value"] + } + } + }, + "regs": { + "ext": "" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "failed to parse URL: [failed to parse Adnuntius endpoint: failed to parse ExtRegs in Adnuntius GDPR check: expect { or n, but found \"]", + "comparison": "literal" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/max-deals-test.json b/adapters/adnuntius/adnuntiustest/supplemental/max-deals-test.json index 1d4c5bf0747..9a2f24bdc45 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/max-deals-test.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/max-deals-test.json @@ -28,11 +28,11 @@ } ] }, - + "httpCalls": [ { "expectedRequest": { - "uri": "http://whatever.url?format=json&tzo=0", + "uri": "http://whatever.url?format=prebid&tzo=0", "body": { "adUnits": [ { @@ -44,7 +44,8 @@ ], "context": "prebid.org", "metaData": {} - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adnuntius/adnuntiustest/supplemental/send-header-information.json b/adapters/adnuntius/adnuntiustest/supplemental/send-header-information.json index bcfecfa8e98..2383cd4e3e7 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/send-header-information.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/send-header-information.json @@ -50,7 +50,7 @@ "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Mobile Safari/537.36" ] }, - "uri": "http://whatever.url?format=json&tzo=0", + "uri": "http://whatever.url?format=prebid&tzo=0", "body": { "adUnits": [ { @@ -63,7 +63,8 @@ "usi": "1kjh3429kjh295jkl" }, "context": "unknown" - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adnuntius/adnuntiustest/supplemental/site-ext.json b/adapters/adnuntius/adnuntiustest/supplemental/site-ext.json new file mode 100644 index 00000000000..ddf37962054 --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/site-ext.json @@ -0,0 +1,114 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl" + }, + "site": { + "ext":{ + "data" : { + "key": ["value"] + } + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=prebid&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id", + "dimensions": [[300,250],[300,600]] + } + ], + "kv": { + "key": ["value"] + }, + "metaData": { + "usi": "1kjh3429kjh295jkl" + }, + "context": "unknown" + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { + "amount": 20.0, + "currency": "NOK" + }, + "adId": "adn-id-1559784094", + "creativeWidth": "980", + "creativeHeight": "240", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "adn-id-1559784094", + "impid": "test-imp-id", + "price": 20000, + "adm": "", + "adid": "adn-id-1559784094", + "adomain": [ + "google.com" + ], + "cid": "q7y9qm5b0xt9htrv", + "crid": "jn9hpzvlsf8cpdmm", + "w": 980, + "h": 240 + }, + "type": "banner" + } + ], + "currency": "NOK" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/size-check.json b/adapters/adnuntius/adnuntiustest/supplemental/size-check.json index c05428c123f..f39586d9b9f 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/size-check.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/size-check.json @@ -28,7 +28,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://whatever.url?format=json&noCookies=true&tzo=0", + "uri": "http://whatever.url?format=prebid&noCookies=true&tzo=0", "body": { "adUnits": [ { @@ -41,7 +41,8 @@ "usi": "1kjh3429kjh295jkl" }, "context": "unknown" - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adnuntius/adnuntiustest/supplemental/status-400.json b/adapters/adnuntius/adnuntiustest/supplemental/status-400.json index f8407b1de5b..695aeb36e0d 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/status-400.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/status-400.json @@ -27,7 +27,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://whatever.url?format=json&tzo=0", + "uri": "http://whatever.url?format=prebid&tzo=0", "body": { "adUnits": [ { @@ -40,7 +40,8 @@ "metaData": { } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/adnuntius/adnuntiustest/supplemental/test-networks.json b/adapters/adnuntius/adnuntiustest/supplemental/test-networks.json index 2e0f0afcbbd..b81e51cd3d9 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/test-networks.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/test-networks.json @@ -32,7 +32,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://whatever.url?format=json&tzo=0", + "uri": "http://whatever.url?format=prebid&tzo=0", "body": { "adUnits": [ { @@ -43,7 +43,8 @@ ], "context": "prebid.org", "metaData": {} - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adnuntius/adnuntiustest/supplemental/user-ext.json b/adapters/adnuntius/adnuntiustest/supplemental/user-ext.json new file mode 100644 index 00000000000..2c2dcac1575 --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/user-ext.json @@ -0,0 +1,113 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "ext":{ + "eids" : [ + { + "source": "idProvider", + "uids": [ + { "id": "userId", "atype": 1, "ext": { "stype": "ppuid" } } + ] + } + ] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=prebid&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id", + "dimensions": [[300,250],[300,600]] + } + ], + "metaData": { + "usi": "userId" + }, + "context": "unknown" + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { + "amount": 20.0, + "currency": "NOK" + }, + "adId": "adn-id-1559784094", + "creativeWidth": "980", + "creativeHeight": "240", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "adn-id-1559784094", + "impid": "test-imp-id", + "price": 20000, + "adm": "", + "adid": "adn-id-1559784094", + "adomain": [ + "google.com" + ], + "cid": "q7y9qm5b0xt9htrv", + "crid": "jn9hpzvlsf8cpdmm", + "w": 980, + "h": 240 + }, + "type": "banner" + } + ], + "currency": "NOK" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/width-error.json b/adapters/adnuntius/adnuntiustest/supplemental/width-error.json index 4f109942b91..96d98a98b85 100644 --- a/adapters/adnuntius/adnuntiustest/supplemental/width-error.json +++ b/adapters/adnuntius/adnuntiustest/supplemental/width-error.json @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://whatever.url?format=json&tzo=0", + "uri": "http://whatever.url?format=prebid&tzo=0", "body": { "adUnits": [ { @@ -43,7 +43,8 @@ "metaData": { } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -81,7 +82,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal number into Go struct field Ad.AdUnits.Ads.CreativeWidth of type string", + "value": "cannot unmarshal adnuntius.Ad.CreativeWidth: expects \" or n, but found 9", "comparison": "literal" } ] diff --git a/adapters/adnuntius/params_test.go b/adapters/adnuntius/params_test.go index c3b42018340..92db0b9e5ad 100644 --- a/adapters/adnuntius/params_test.go +++ b/adapters/adnuntius/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // This file actually intends to test static/bidder-params/adnuntius.json diff --git a/adapters/adocean/adocean.go b/adapters/adocean/adocean.go index a4e6223be6d..82f32001596 100644 --- a/adapters/adocean/adocean.go +++ b/adapters/adocean/adocean.go @@ -1,7 +1,6 @@ package adocean import ( - "encoding/json" "errors" "fmt" "math/rand" @@ -13,12 +12,13 @@ import ( "strings" "text/template" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) const adapterVersion = "1.3.0" @@ -57,6 +57,7 @@ type requestData struct { Url *url.URL Headers *http.Header SlaveSizes map[string]string + ImpIDs []string } // Builder builds a new instance of the AdOcean adapter for the given bidder with the given config. @@ -90,7 +91,7 @@ func (a *AdOceanAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada consentString := "" if request.User != nil { var extUser openrtb_ext.ExtUser - if err := json.Unmarshal(request.User.Ext, &extUser); err == nil { + if err := jsonutil.Unmarshal(request.User.Ext, &extUser); err == nil { consentString = extUser.Consent } } @@ -111,6 +112,7 @@ func (a *AdOceanAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada Method: "GET", Uri: requestData.Url.String(), Headers: *requestData.Headers, + ImpIDs: requestData.ImpIDs, }) } @@ -124,14 +126,14 @@ func (a *AdOceanAdapter) addNewBid( consentString string, ) ([]*requestData, error) { var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { return requestsData, &errortypes.BadInput{ Message: "Error parsing bidderExt object", } } var adOceanExt openrtb_ext.ExtImpAdOcean - if err := json.Unmarshal(bidderExt.Bidder, &adOceanExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &adOceanExt); err != nil { return requestsData, &errortypes.BadInput{ Message: "Error parsing adOceanExt parameters", } @@ -160,6 +162,7 @@ func (a *AdOceanAdapter) addNewBid( Url: url, Headers: a.formHeaders(request), SlaveSizes: slaveSizes, + ImpIDs: []string{imp.ID}, }) return requestsData, nil @@ -185,6 +188,7 @@ func addToExistingRequest(requestsData []*requestData, newParams *openrtb_ext.Ex newUrl.RawQuery = queryParams.Encode() if len(newUrl.String()) < maxUriLength { requestData.Url = &newUrl + requestData.ImpIDs = append(requestData.ImpIDs, auctionID) return true } @@ -345,7 +349,7 @@ func (a *AdOceanAdapter) MakeBids( auctionIDs := queryParams["aid"] bidResponses := make([]ResponseAdUnit, 0) - if err := json.Unmarshal(response.Body, &bidResponses); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResponses); err != nil { return nil, []error{err} } diff --git a/adapters/adocean/adocean_test.go b/adapters/adocean/adocean_test.go index 8d646cb9ca0..4fb784bfc01 100644 --- a/adapters/adocean/adocean_test.go +++ b/adapters/adocean/adocean_test.go @@ -3,9 +3,9 @@ package adocean import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adocean/adoceantest/exemplary/multi-banner-impression.json b/adapters/adocean/adoceantest/exemplary/multi-banner-impression.json index f70bc46749c..6eac26e0d03 100644 --- a/adapters/adocean/adoceantest/exemplary/multi-banner-impression.json +++ b/adapters/adocean/adoceantest/exemplary/multi-banner-impression.json @@ -72,7 +72,8 @@ }, "httpCalls": [{ "expectedRequest": { - "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aid=adoceanmyaowafpdwlrks%3Asecod-twelve&aosspsizes=myaowafpdwlrks~300x250-myaozpniqismex~300x250_320x600&devmake=&devmodel=&devos=&devosv=&dpidmd5=f2ba45ece57cff9477d5a8083b138c9g&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0" + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aid=adoceanmyaowafpdwlrks%3Asecod-twelve&aosspsizes=myaowafpdwlrks~300x250-myaozpniqismex~300x250_320x600&devmake=&devmodel=&devos=&devosv=&dpidmd5=f2ba45ece57cff9477d5a8083b138c9g&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0", + "impIDs":["ao-test","secod-twelve"] }, "mockResponse": { "status": 200, diff --git a/adapters/adocean/adoceantest/exemplary/single-banner-impression.json b/adapters/adocean/adoceantest/exemplary/single-banner-impression.json index 19b9528152d..9f5fa2a3499 100644 --- a/adapters/adocean/adoceantest/exemplary/single-banner-impression.json +++ b/adapters/adocean/adoceantest/exemplary/single-banner-impression.json @@ -60,7 +60,8 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aosspsizes=myaozpniqismex~300x250&devmake=&devmodel=&devos=&devosv=&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&ifa=f2ba45ece57cff9477d5a8083b138c9a&nc=1&nosecure=1&pbsrv_v=1.3.0" + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aosspsizes=myaozpniqismex~300x250&devmake=&devmodel=&devos=&devosv=&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&ifa=f2ba45ece57cff9477d5a8083b138c9a&nc=1&nosecure=1&pbsrv_v=1.3.0", + "impIDs":["ao-test"] }, "mockResponse": { "status": 200, diff --git a/adapters/adocean/adoceantest/supplemental/app.json b/adapters/adocean/adoceantest/supplemental/app.json index 994495d5291..28c67bdf818 100644 --- a/adapters/adocean/adoceantest/supplemental/app.json +++ b/adapters/adocean/adoceantest/supplemental/app.json @@ -54,7 +54,8 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aosspsizes=myaozpniqismex~300x250&app=1&appbundle=12345&appdomain=example.com&appname=Weather+App&devmake=&devmodel=&devos=iOS&devosv=&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&ifa=f2ba45ece57cff9477d5a8083b138c9a&nc=1&nosecure=1&pbsrv_v=1.3.0" + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aosspsizes=myaozpniqismex~300x250&app=1&appbundle=12345&appdomain=example.com&appname=Weather+App&devmake=&devmodel=&devos=iOS&devosv=&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&ifa=f2ba45ece57cff9477d5a8083b138c9a&nc=1&nosecure=1&pbsrv_v=1.3.0", + "impIDs":["ao-test"] }, "mockResponse": { "status": 200, @@ -64,7 +65,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type []adocean.ResponseAdUnit", + "value": "decode slice: expect [ or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/adocean/adoceantest/supplemental/bad-response.json b/adapters/adocean/adoceantest/supplemental/bad-response.json index 43e62d6f794..eb1bcb6ef0c 100644 --- a/adapters/adocean/adoceantest/supplemental/bad-response.json +++ b/adapters/adocean/adoceantest/supplemental/bad-response.json @@ -49,7 +49,8 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aosspsizes=myaozpniqismex~300x250&devmake=&devmodel=&devos=&devosv=&dpidmd5=&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0" + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aosspsizes=myaozpniqismex~300x250&devmake=&devmodel=&devos=&devosv=&dpidmd5=&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0", + "impIDs":["ao-test"] }, "mockResponse": { "status": 200, @@ -59,7 +60,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type []adocean.ResponseAdUnit", + "value": "decode slice: expect [ or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/adocean/adoceantest/supplemental/encode-error.json b/adapters/adocean/adoceantest/supplemental/encode-error.json index 98b977fd22b..32a0565da26 100644 --- a/adapters/adocean/adoceantest/supplemental/encode-error.json +++ b/adapters/adocean/adoceantest/supplemental/encode-error.json @@ -49,7 +49,8 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aosspsizes=myaozpniqismex~300x250&devmake=&devmodel=&devos=&devosv=&dpidmd5=&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0" + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aosspsizes=myaozpniqismex~300x250&devmake=&devmodel=&devos=&devosv=&dpidmd5=&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0", + "impIDs":["ao-test"] }, "mockResponse": { "status": 200, diff --git a/adapters/adocean/adoceantest/supplemental/network-error.json b/adapters/adocean/adoceantest/supplemental/network-error.json index c18ddf16bfc..5910a5626b6 100644 --- a/adapters/adocean/adoceantest/supplemental/network-error.json +++ b/adapters/adocean/adoceantest/supplemental/network-error.json @@ -49,7 +49,8 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aosspsizes=myaozpniqismex~300x250&devmake=&devmodel=&devos=&devosv=&dpidmd5=&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0" + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aosspsizes=myaozpniqismex~300x250&devmake=&devmodel=&devos=&devosv=&dpidmd5=&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0", + "impIDs":["ao-test"] }, "mockResponse": { "status": 500, diff --git a/adapters/adocean/adoceantest/supplemental/no-bid.json b/adapters/adocean/adoceantest/supplemental/no-bid.json index caf2cc9e3b5..a2c9b9c7ef9 100644 --- a/adapters/adocean/adoceantest/supplemental/no-bid.json +++ b/adapters/adocean/adoceantest/supplemental/no-bid.json @@ -83,7 +83,8 @@ }, "httpCalls": [{ "expectedRequest": { - "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aid=adoceanmyaowafpdwlrks%3Aao-test-two&aosspsizes=myaowafpdwlrks~300x250-myaozpniqismex~300x250&devmake=&devmodel=&devos=&devosv=&dpidmd5=&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0" + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aid=adoceanmyaowafpdwlrks%3Aao-test-two&aosspsizes=myaowafpdwlrks~300x250-myaozpniqismex~300x250&devmake=&devmodel=&devos=&devosv=&dpidmd5=&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0", + "impIDs":["ao-test","ao-test-two"] }, "mockResponse": { "status": 200, @@ -108,7 +109,8 @@ } }, { "expectedRequest": { - "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaowafpdwlrks%3Aao-test-three&aosspsizes=myaowafpdwlrks~300x250&devmake=&devmodel=&devos=&devosv=&dpidmd5=&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0" + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaowafpdwlrks%3Aao-test-three&aosspsizes=myaowafpdwlrks~300x250&devmake=&devmodel=&devos=&devosv=&dpidmd5=&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0", + "impIDs":["ao-test-three"] }, "mockResponse": { "status": 200, diff --git a/adapters/adocean/adoceantest/supplemental/no-sizes.json b/adapters/adocean/adoceantest/supplemental/no-sizes.json index 465698fdac4..c469f5e095d 100644 --- a/adapters/adocean/adoceantest/supplemental/no-sizes.json +++ b/adapters/adocean/adoceantest/supplemental/no-sizes.json @@ -72,7 +72,8 @@ }, "httpCalls": [{ "expectedRequest": { - "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aid=adoceanmyaowafpdwlrks%3Aao-test-two&devmake=&devmodel=&devos=&devosv=&dpidmd5=&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0" + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aid=adoceanmyaowafpdwlrks%3Aao-test-two&devmake=&devmodel=&devos=&devosv=&dpidmd5=&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0", + "impIDs":["ao-test","ao-test-two"] }, "mockResponse": { "status": 200, @@ -106,7 +107,8 @@ } }, { "expectedRequest": { - "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaowafpdwlrks%3Aao-test-three&aosspsizes=myaowafpdwlrks~300x250&devmake=&devmodel=&devos=&devosv=&dpidmd5=&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0" + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaowafpdwlrks%3Aao-test-three&aosspsizes=myaowafpdwlrks~300x250&devmake=&devmodel=&devos=&devosv=&dpidmd5=&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0", + "impIDs":["ao-test-three"] }, "mockResponse": { "status": 200, diff --git a/adapters/adocean/adoceantest/supplemental/requests-merge.json b/adapters/adocean/adoceantest/supplemental/requests-merge.json index 9021bf66f16..240c74bc776 100644 --- a/adapters/adocean/adoceantest/supplemental/requests-merge.json +++ b/adapters/adocean/adoceantest/supplemental/requests-merge.json @@ -83,7 +83,8 @@ }, "httpCalls": [{ "expectedRequest": { - "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aid=adoceanmyaowafpdwlrks%3Aao-test-two&aosspsizes=myaowafpdwlrks~300x250-myaozpniqismex~300x250&devmake=&devmodel=&devos=&devosv=&dpidmd5=&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0" + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaozpniqismex%3Aao-test&aid=adoceanmyaowafpdwlrks%3Aao-test-two&aosspsizes=myaowafpdwlrks~300x250-myaozpniqismex~300x250&devmake=&devmodel=&devos=&devosv=&dpidmd5=&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0", + "impIDs":["ao-test","ao-test-two"] }, "mockResponse": { "status": 200, @@ -117,7 +118,8 @@ } }, { "expectedRequest": { - "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaowafpdwlrks%3Aao-test-three&aosspsizes=myaowafpdwlrks~300x250&devmake=&devmodel=&devos=&devosv=&dpidmd5=&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0" + "uri": "https://myao.adocean.pl/_10000000/ad.json?aid=adoceanmyaowafpdwlrks%3Aao-test-three&aosspsizes=myaowafpdwlrks~300x250&devmake=&devmodel=&devos=&devosv=&dpidmd5=&gdpr=1&gdpr_consent=COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1&pbsrv_v=1.3.0", + "impIDs":["ao-test-three"] }, "mockResponse": { "status": 200, diff --git a/adapters/adocean/params_test.go b/adapters/adocean/params_test.go index 18625b5e85e..0840d10847c 100644 --- a/adapters/adocean/params_test.go +++ b/adapters/adocean/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/adoppler/adoppler.go b/adapters/adoppler/adoppler.go index 90070e8145d..ace52b93b1d 100644 --- a/adapters/adoppler/adoppler.go +++ b/adapters/adoppler/adoppler.go @@ -8,12 +8,13 @@ import ( "net/url" "text/template" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) const DefaultClient = "app" @@ -65,7 +66,7 @@ func (ads *AdopplerAdapter) MakeRequests( for _, imp := range req.Imp { ext, err := unmarshalExt(imp.Ext) if err != nil { - errs = append(errs, &errortypes.BadInput{err.Error()}) + errs = append(errs, &errortypes.BadInput{Message: err.Error()}) continue } @@ -83,7 +84,7 @@ func (ads *AdopplerAdapter) MakeRequests( if err != nil { e := fmt.Sprintf("Unable to build bid URI: %s", err.Error()) - errs = append(errs, &errortypes.BadInput{e}) + errs = append(errs, &errortypes.BadInput{Message: e}) continue } data := &adapters.RequestData{ @@ -91,6 +92,7 @@ func (ads *AdopplerAdapter) MakeRequests( Uri: uri, Body: body, Headers: bidHeaders, + ImpIDs: openrtb_ext.GetImpIDs(r.Imp), } datas = append(datas, data) } @@ -110,20 +112,20 @@ func (ads *AdopplerAdapter) MakeBids( return nil, nil } if resp.StatusCode == http.StatusBadRequest { - return nil, []error{&errortypes.BadInput{"bad request"}} + return nil, []error{&errortypes.BadInput{Message: "bad request"}} } if resp.StatusCode != http.StatusOK { err := &errortypes.BadServerResponse{ - fmt.Sprintf("unexpected status: %d", resp.StatusCode), + Message: fmt.Sprintf("unexpected status: %d", resp.StatusCode), } return nil, []error{err} } var bidResp openrtb2.BidResponse - err := json.Unmarshal(resp.Body, &bidResp) + err := jsonutil.Unmarshal(resp.Body, &bidResp) if err != nil { err := &errortypes.BadServerResponse{ - fmt.Sprintf("invalid body: %s", err.Error()), + Message: fmt.Sprintf("invalid body: %s", err.Error()), } return nil, []error{err} } @@ -132,7 +134,7 @@ func (ads *AdopplerAdapter) MakeBids( for _, imp := range intReq.Imp { if _, ok := impTypes[imp.ID]; ok { return nil, []error{&errortypes.BadInput{ - fmt.Sprintf("duplicate $.imp.id %s", imp.ID), + Message: fmt.Sprintf("duplicate $.imp.id %s", imp.ID), }} } if imp.Banner != nil { @@ -145,7 +147,7 @@ func (ads *AdopplerAdapter) MakeBids( impTypes[imp.ID] = openrtb_ext.BidTypeNative } else { return nil, []error{&errortypes.BadInput{ - "one of $.imp.banner, $.imp.video, $.imp.audio and $.imp.native field required", + Message: "one of $.imp.banner, $.imp.video, $.imp.audio and $.imp.native field required", }} } } @@ -156,7 +158,7 @@ func (ads *AdopplerAdapter) MakeBids( tp, ok := impTypes[bid.ImpID] if !ok { err := &errortypes.BadServerResponse{ - fmt.Sprintf("unknown impid: %s", bid.ImpID), + Message: fmt.Sprintf("unknown impid: %s", bid.ImpID), } return nil, []error{err} } @@ -165,11 +167,11 @@ func (ads *AdopplerAdapter) MakeBids( if tp == openrtb_ext.BidTypeVideo { adsExt, err := unmarshalAdsExt(bid.Ext) if err != nil { - return nil, []error{&errortypes.BadServerResponse{err.Error()}} + return nil, []error{&errortypes.BadServerResponse{Message: err.Error()}} } if adsExt == nil || adsExt.Video == nil { return nil, []error{&errortypes.BadServerResponse{ - "$.seatbid.bid.ext.ads.video required", + Message: "$.seatbid.bid.ext.ads.video required", }} } bidVideo = &openrtb_ext.ExtBidPrebidVideo{ @@ -205,13 +207,13 @@ func (ads *AdopplerAdapter) bidUri(ext *openrtb_ext.ExtImpAdoppler) (string, err func unmarshalExt(ext json.RawMessage) (*openrtb_ext.ExtImpAdoppler, error) { var bext adapters.ExtImpBidder - err := json.Unmarshal(ext, &bext) + err := jsonutil.Unmarshal(ext, &bext) if err != nil { return nil, err } var adsExt openrtb_ext.ExtImpAdoppler - err = json.Unmarshal(bext.Bidder, &adsExt) + err = jsonutil.Unmarshal(bext.Bidder, &adsExt) if err != nil { return nil, err } @@ -227,7 +229,7 @@ func unmarshalAdsExt(ext json.RawMessage) (*adsImpExt, error) { var e struct { Ads *adsImpExt `json:"ads"` } - err := json.Unmarshal(ext, &e) + err := jsonutil.Unmarshal(ext, &e) return e.Ads, err } diff --git a/adapters/adoppler/adoppler_test.go b/adapters/adoppler/adoppler_test.go index 9f026b2f29c..3c6888f3124 100644 --- a/adapters/adoppler/adoppler_test.go +++ b/adapters/adoppler/adoppler_test.go @@ -3,9 +3,9 @@ package adoppler import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adoppler/adopplertest/exemplary/custom-client.json b/adapters/adoppler/adopplertest/exemplary/custom-client.json index 6bb32f71546..fe92c363903 100644 --- a/adapters/adoppler/adopplertest/exemplary/custom-client.json +++ b/adapters/adoppler/adopplertest/exemplary/custom-client.json @@ -38,7 +38,8 @@ } } ] - } + }, + "impIDs":["imp1"] }, "mockResponse":{ "status":200, diff --git a/adapters/adoppler/adopplertest/exemplary/default-client.json b/adapters/adoppler/adopplertest/exemplary/default-client.json index 25fb71970e0..dd53208f7d8 100644 --- a/adapters/adoppler/adopplertest/exemplary/default-client.json +++ b/adapters/adoppler/adopplertest/exemplary/default-client.json @@ -36,7 +36,8 @@ } } ] - } + }, + "impIDs":["imp1"] }, "mockResponse":{ "status":200, diff --git a/adapters/adoppler/adopplertest/exemplary/multiimp.json b/adapters/adoppler/adopplertest/exemplary/multiimp.json index 6eebbe43071..bdbef5a4af0 100644 --- a/adapters/adoppler/adopplertest/exemplary/multiimp.json +++ b/adapters/adoppler/adopplertest/exemplary/multiimp.json @@ -61,7 +61,8 @@ } } ] - } + }, + "impIDs":["imp1"] }, "mockResponse":{ "status":200, @@ -104,7 +105,8 @@ } } ] - } + }, + "impIDs":["imp2"] }, "mockResponse":{ "status":200, @@ -155,7 +157,8 @@ } } ] - } + }, + "impIDs":["imp3"] }, "mockResponse":{ "status":204, diff --git a/adapters/adoppler/adopplertest/supplemental/bad-request.json b/adapters/adoppler/adopplertest/supplemental/bad-request.json index ae515e01e18..c2e38460202 100644 --- a/adapters/adoppler/adopplertest/supplemental/bad-request.json +++ b/adapters/adoppler/adopplertest/supplemental/bad-request.json @@ -36,7 +36,8 @@ } } ] - } + }, + "impIDs":["imp1"] }, "mockResponse":{ "status":400, diff --git a/adapters/adoppler/adopplertest/supplemental/duplicate-imp.json b/adapters/adoppler/adopplertest/supplemental/duplicate-imp.json index d6f17a6bb2c..cc24ae03a2f 100644 --- a/adapters/adoppler/adopplertest/supplemental/duplicate-imp.json +++ b/adapters/adoppler/adopplertest/supplemental/duplicate-imp.json @@ -48,7 +48,8 @@ } } ] - } + }, + "impIDs":["imp1"] }, "mockResponse":{ "status":200, @@ -89,7 +90,8 @@ } } ] - } + }, + "impIDs":["imp1"] }, "mockResponse":{ "status":200, diff --git a/adapters/adoppler/adopplertest/supplemental/invalid-impid.json b/adapters/adoppler/adopplertest/supplemental/invalid-impid.json index b5f062ac94a..e94a0e0c406 100644 --- a/adapters/adoppler/adopplertest/supplemental/invalid-impid.json +++ b/adapters/adoppler/adopplertest/supplemental/invalid-impid.json @@ -36,7 +36,8 @@ } } ] - } + }, + "impIDs":["imp1"] }, "mockResponse":{ "status":200, diff --git a/adapters/adoppler/adopplertest/supplemental/invalid-response.json b/adapters/adoppler/adopplertest/supplemental/invalid-response.json index 83aa35f0ec1..84342feaa0e 100644 --- a/adapters/adoppler/adopplertest/supplemental/invalid-response.json +++ b/adapters/adoppler/adopplertest/supplemental/invalid-response.json @@ -36,7 +36,8 @@ } } ] - } + }, + "impIDs":["imp1"] }, "mockResponse":{ "status":200, @@ -49,7 +50,7 @@ ], "expectedMakeBidsErrors":[ { - "value":"invalid body: json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "value":"invalid body: expect { or n, but found \"", "comparison":"literal" } ] diff --git a/adapters/adoppler/adopplertest/supplemental/invalid-video-ext.json b/adapters/adoppler/adopplertest/supplemental/invalid-video-ext.json index c08cdca5cee..6bab4036c5c 100644 --- a/adapters/adoppler/adopplertest/supplemental/invalid-video-ext.json +++ b/adapters/adoppler/adopplertest/supplemental/invalid-video-ext.json @@ -54,7 +54,8 @@ } } ] - } + }, + "impIDs":["imp1"] }, "mockResponse":{ "status":200, @@ -104,7 +105,8 @@ } } ] - } + }, + "impIDs":["imp2"] }, "mockResponse":{ "status":200, @@ -138,7 +140,7 @@ "comparison":"literal" }, { - "value":"json: cannot unmarshal string into Go value of type struct { Ads *adoppler.adsImpExt \"json:\\\"ads\\\"\" }", + "value":"expect { or n, but found \"", "comparison":"literal" } ] diff --git a/adapters/adoppler/adopplertest/supplemental/no-bid.json b/adapters/adoppler/adopplertest/supplemental/no-bid.json index 08a29481350..9135b4c5f2f 100644 --- a/adapters/adoppler/adopplertest/supplemental/no-bid.json +++ b/adapters/adoppler/adopplertest/supplemental/no-bid.json @@ -36,7 +36,8 @@ } } ] - } + }, + "impIDs":["imp1"] }, "mockResponse":{ "status":204, diff --git a/adapters/adoppler/adopplertest/supplemental/server-error.json b/adapters/adoppler/adopplertest/supplemental/server-error.json index 604b83e74a6..dcd45e3718d 100644 --- a/adapters/adoppler/adopplertest/supplemental/server-error.json +++ b/adapters/adoppler/adopplertest/supplemental/server-error.json @@ -36,7 +36,8 @@ } } ] - } + }, + "impIDs":["imp1"] }, "mockResponse":{ "status":500, diff --git a/adapters/adot/adot.go b/adapters/adot/adot.go index fbba9fee467..fc32ef6cae4 100644 --- a/adapters/adot/adot.go +++ b/adapters/adot/adot.go @@ -7,11 +7,12 @@ import ( "strconv" "strings" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { @@ -60,6 +61,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E Uri: endpoint, Body: reqJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }}, nil } @@ -83,7 +85,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest } var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -115,7 +117,7 @@ func getMediaTypeForBid(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { } var impExt adotBidExt - if err := json.Unmarshal(bid.Ext, &impExt); err == nil { + if err := jsonutil.Unmarshal(bid.Ext, &impExt); err == nil { switch impExt.Adot.MediaType { case string(openrtb_ext.BidTypeBanner): return openrtb_ext.BidTypeBanner, nil @@ -143,11 +145,11 @@ func resolveMacros(bid *openrtb2.Bid) { func getImpAdotExt(imp *openrtb2.Imp) *openrtb_ext.ExtImpAdot { var extImpAdot openrtb_ext.ExtImpAdot var extBidder adapters.ExtImpBidder - err := json.Unmarshal(imp.Ext, &extBidder) + err := jsonutil.Unmarshal(imp.Ext, &extBidder) if err != nil { return nil } - err = json.Unmarshal(extBidder.Bidder, &extImpAdot) + err = jsonutil.Unmarshal(extBidder.Bidder, &extImpAdot) if err != nil { return nil } diff --git a/adapters/adot/adot_test.go b/adapters/adot/adot_test.go index 2f35f2a85fa..d5d5afc6c47 100644 --- a/adapters/adot/adot_test.go +++ b/adapters/adot/adot_test.go @@ -4,11 +4,11 @@ import ( "encoding/json" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adot/adottest/exemplary/simple-banner.json b/adapters/adot/adottest/exemplary/simple-banner.json index 185addfcaef..83b4b9fcd68 100644 --- a/adapters/adot/adottest/exemplary/simple-banner.json +++ b/adapters/adot/adottest/exemplary/simple-banner.json @@ -43,7 +43,8 @@ } } ] - } + }, + "impIDs":["test-imp-banner-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adot/adottest/exemplary/simple-interstitial.json b/adapters/adot/adottest/exemplary/simple-interstitial.json index 09878984e76..0b43c7a46d7 100644 --- a/adapters/adot/adottest/exemplary/simple-interstitial.json +++ b/adapters/adot/adottest/exemplary/simple-interstitial.json @@ -46,7 +46,8 @@ } } ] - } + }, + "impIDs":["test-imp-inter-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adot/adottest/exemplary/simple-native.json b/adapters/adot/adottest/exemplary/simple-native.json index 9ad26b260ac..d1fff99b08d 100644 --- a/adapters/adot/adottest/exemplary/simple-native.json +++ b/adapters/adot/adottest/exemplary/simple-native.json @@ -32,7 +32,8 @@ } } ] - } + }, + "impIDs":["test-imp-native-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adot/adottest/exemplary/simple-video.json b/adapters/adot/adottest/exemplary/simple-video.json index f4620a9e2cf..5fabaf8b7ac 100644 --- a/adapters/adot/adottest/exemplary/simple-video.json +++ b/adapters/adot/adottest/exemplary/simple-video.json @@ -44,7 +44,8 @@ } } ] - } + }, + "impIDs":["test-imp-video-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adot/adottest/supplemental/ext-bidder-empty.json b/adapters/adot/adottest/supplemental/ext-bidder-empty.json index b3fc956f3da..69ad90179f8 100644 --- a/adapters/adot/adottest/supplemental/ext-bidder-empty.json +++ b/adapters/adot/adottest/supplemental/ext-bidder-empty.json @@ -47,7 +47,8 @@ } } ] - } + }, + "impIDs":["test-imp-publishPath-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adot/adottest/supplemental/ext-bidder-publisher-path.json b/adapters/adot/adottest/supplemental/ext-bidder-publisher-path.json index bf69d12a8d9..312eb5f4732 100644 --- a/adapters/adot/adottest/supplemental/ext-bidder-publisher-path.json +++ b/adapters/adot/adottest/supplemental/ext-bidder-publisher-path.json @@ -50,7 +50,8 @@ } } ] - } + }, + "impIDs":["test-imp-publishPath-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adot/adottest/supplemental/simple-audio.json b/adapters/adot/adottest/supplemental/simple-audio.json index 38be60ed893..e6d313a3a41 100644 --- a/adapters/adot/adottest/supplemental/simple-audio.json +++ b/adapters/adot/adottest/supplemental/simple-audio.json @@ -34,7 +34,8 @@ } } ] - } + }, + "impIDs":["unsupported-audio-imp"] }, "mockResponse": { "status": 200, diff --git a/adapters/adot/adottest/supplemental/simple-parallax.json b/adapters/adot/adottest/supplemental/simple-parallax.json index 4ee2ebc22d0..a9356e3f066 100644 --- a/adapters/adot/adottest/supplemental/simple-parallax.json +++ b/adapters/adot/adottest/supplemental/simple-parallax.json @@ -48,7 +48,8 @@ } } ] - } + }, + "impIDs":["test-imp-parallax-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adot/adottest/supplemental/status_204.json b/adapters/adot/adottest/supplemental/status_204.json index 44a895b5c24..898d82f38b5 100644 --- a/adapters/adot/adottest/supplemental/status_204.json +++ b/adapters/adot/adottest/supplemental/status_204.json @@ -26,7 +26,8 @@ } } ] - } + }, + "impIDs":["test-imp-banner-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/adot/adottest/supplemental/status_400.json b/adapters/adot/adottest/supplemental/status_400.json index 22328fd9908..b39eb6b4325 100644 --- a/adapters/adot/adottest/supplemental/status_400.json +++ b/adapters/adot/adottest/supplemental/status_400.json @@ -44,7 +44,8 @@ } } ] - } + }, + "impIDs":["test-imp-banner-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/adot/adottest/supplemental/status_500.json b/adapters/adot/adottest/supplemental/status_500.json index f749ecc8a69..dd2873e299e 100644 --- a/adapters/adot/adottest/supplemental/status_500.json +++ b/adapters/adot/adottest/supplemental/status_500.json @@ -44,7 +44,8 @@ } } ] - } + }, + "impIDs":["test-imp-banner-id"] }, "mockResponse": { "status": 500, diff --git a/adapters/adot/adottest/supplemental/unmarshal_error.json b/adapters/adot/adottest/supplemental/unmarshal_error.json index 557fb16cb43..68154164f47 100644 --- a/adapters/adot/adottest/supplemental/unmarshal_error.json +++ b/adapters/adot/adottest/supplemental/unmarshal_error.json @@ -44,7 +44,8 @@ } } ] - } + }, + "impIDs":["test-imp-banner-id"] }, "mockResponse": { "status": 200, @@ -55,7 +56,7 @@ "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/adot/params_test.go b/adapters/adot/params_test.go index 6760419b470..99f8c9d932c 100644 --- a/adapters/adot/params_test.go +++ b/adapters/adot/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // This file actually intends to test static/bidder-params/adot.json diff --git a/adapters/adpone/adpone.go b/adapters/adpone/adpone.go index bf7ffb93992..bdf72398897 100644 --- a/adapters/adpone/adpone.go +++ b/adapters/adpone/adpone.go @@ -5,12 +5,13 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) // Builder builds a new instance of the Adpone adapter for the given bidder with the given config. @@ -35,11 +36,11 @@ func (adapter *adponeAdapter) MakeRequests( if len(openRTBRequest.Imp) > 0 { var imp = &openRTBRequest.Imp[0] var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { errs = append(errs, newBadInputError(err.Error())) } var ttxExt openrtb_ext.ExtAdpone - if err := json.Unmarshal(bidderExt.Bidder, &ttxExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &ttxExt); err != nil { errs = append(errs, newBadInputError(err.Error())) } } @@ -65,6 +66,7 @@ func (adapter *adponeAdapter) MakeRequests( Uri: adapter.endpoint, Body: openRTBRequestJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(openRTBRequest.Imp), } requestsToBidder = append(requestsToBidder, requestToBidder) @@ -100,7 +102,7 @@ func (adapter *adponeAdapter) MakeBids( } var openRTBBidderResponse openrtb2.BidResponse - if err := json.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil { + if err := jsonutil.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil { return nil, []error{err} } diff --git a/adapters/adpone/adpone_test.go b/adapters/adpone/adpone_test.go index 7b01a382587..e90055b2e65 100644 --- a/adapters/adpone/adpone_test.go +++ b/adapters/adpone/adpone_test.go @@ -3,9 +3,9 @@ package adpone import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) const testsDir = "adponetest" diff --git a/adapters/adpone/adponetest/exemplary/simple-banner.json b/adapters/adpone/adponetest/exemplary/simple-banner.json index c481829b898..b6608558a40 100644 --- a/adapters/adpone/adponetest/exemplary/simple-banner.json +++ b/adapters/adpone/adponetest/exemplary/simple-banner.json @@ -42,7 +42,8 @@ } } }] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adpone/adponetest/supplemental/bad_response.json b/adapters/adpone/adponetest/supplemental/bad_response.json index 68da7064b97..2200f89ecd4 100644 --- a/adapters/adpone/adponetest/supplemental/bad_response.json +++ b/adapters/adpone/adponetest/supplemental/bad_response.json @@ -45,7 +45,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -56,7 +57,7 @@ "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/adpone/adponetest/supplemental/status_204.json b/adapters/adpone/adponetest/supplemental/status_204.json index 0702c103332..950e5c0d5fe 100644 --- a/adapters/adpone/adponetest/supplemental/status_204.json +++ b/adapters/adpone/adponetest/supplemental/status_204.json @@ -43,7 +43,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/adpone/adponetest/supplemental/status_400.json b/adapters/adpone/adponetest/supplemental/status_400.json index 3d3914bad16..30f9f458698 100644 --- a/adapters/adpone/adponetest/supplemental/status_400.json +++ b/adapters/adpone/adponetest/supplemental/status_400.json @@ -45,7 +45,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/adpone/adponetest/supplemental/status_418.json b/adapters/adpone/adponetest/supplemental/status_418.json index cf14f728846..ad5245b2c4f 100644 --- a/adapters/adpone/adponetest/supplemental/status_418.json +++ b/adapters/adpone/adponetest/supplemental/status_418.json @@ -45,7 +45,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 418, diff --git a/adapters/adprime/adprime.go b/adapters/adprime/adprime.go index 836abe26d83..a4c041ae325 100644 --- a/adapters/adprime/adprime.go +++ b/adapters/adprime/adprime.go @@ -6,11 +6,12 @@ import ( "net/http" "strings" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) // AdprimeAdapter struct @@ -40,13 +41,13 @@ func (a *AdprimeAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada for _, imp := range request.Imp { reqCopy.Imp = []openrtb2.Imp{imp} - err = json.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt) + err = jsonutil.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt) if err != nil { errs = append(errs, err) return nil, errs } - err = json.Unmarshal(bidderExt.Bidder, &adprimeExt) + err = jsonutil.Unmarshal(bidderExt.Bidder, &adprimeExt) if err != nil { errs = append(errs, err) return nil, errs @@ -115,6 +116,7 @@ func (a *AdprimeAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.Re Uri: a.URI, Body: reqJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }, errs } @@ -140,7 +142,7 @@ func (a *AdprimeAdapter) MakeBids(internalRequest *openrtb2.BidRequest, external var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -148,7 +150,8 @@ func (a *AdprimeAdapter) MakeBids(internalRequest *openrtb2.BidRequest, external for _, sb := range bidResp.SeatBid { for i := range sb.Bid { - bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) + bidType, err := getBidMediaType(&sb.Bid[i]) + if err != nil { errs = append(errs, err) } else { @@ -163,22 +166,15 @@ func (a *AdprimeAdapter) MakeBids(internalRequest *openrtb2.BidRequest, external return bidResponse, errs } -func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { - for _, imp := range imps { - if imp.ID == impID { - if imp.Banner != nil { - return openrtb_ext.BidTypeBanner, nil - } - if imp.Video != nil { - return openrtb_ext.BidTypeVideo, nil - } - if imp.Native != nil { - return openrtb_ext.BidTypeNative, nil - } - } - } - - return "", &errortypes.BadInput{ - Message: fmt.Sprintf("Failed to find impression \"%s\"", impID), +func getBidMediaType(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return "", fmt.Errorf("Unable to fetch mediaType in multi-format: %s", bid.ImpID) } } diff --git a/adapters/adprime/adprime_test.go b/adapters/adprime/adprime_test.go index e5cf7df8df5..0b238b5bee0 100644 --- a/adapters/adprime/adprime_test.go +++ b/adapters/adprime/adprime_test.go @@ -3,9 +3,9 @@ package adprime import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adprime/adprimetest/exemplary/simple-banner.json b/adapters/adprime/adprimetest/exemplary/simple-banner.json index e96abe5cff1..20351d8cf0c 100644 --- a/adapters/adprime/adprimetest/exemplary/simple-banner.json +++ b/adapters/adprime/adprimetest/exemplary/simple-banner.json @@ -72,7 +72,8 @@ "ip": "123.123.123.123", "ua": "iPad" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -91,6 +92,7 @@ "dealid": "test_dealid", "w": 300, "h": 250, + "mtype": 1, "ext": { "prebid": { "type": "banner" @@ -120,6 +122,7 @@ "dealid": "test_dealid", "w": 300, "h": 250, + "mtype": 1, "ext": { "prebid": { "type": "banner" diff --git a/adapters/adprime/adprimetest/exemplary/simple-native.json b/adapters/adprime/adprimetest/exemplary/simple-native.json index 0ea8a0a1e3f..41c187b361c 100644 --- a/adapters/adprime/adprimetest/exemplary/simple-native.json +++ b/adapters/adprime/adprimetest/exemplary/simple-native.json @@ -56,7 +56,8 @@ "ip": "123.123.123.123", "ua": "iPad" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -75,6 +76,7 @@ "dealid": "test_dealid", "w": 300, "h": 250, + "mtype": 4, "ext": { "prebid": { "type": "native" @@ -104,6 +106,7 @@ "dealid": "test_dealid", "w": 300, "h": 250, + "mtype": 4, "ext": { "prebid": { "type": "native" diff --git a/adapters/adprime/adprimetest/exemplary/simple-video.json b/adapters/adprime/adprimetest/exemplary/simple-video.json index 164e3040b1d..c7f3aacf084 100644 --- a/adapters/adprime/adprimetest/exemplary/simple-video.json +++ b/adapters/adprime/adprimetest/exemplary/simple-video.json @@ -70,7 +70,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -87,6 +88,7 @@ "cid": "test_cid", "crid": "test_crid", "dealid": "test_dealid", + "mtype": 2, "ext": { "prebid": { "type": "video" @@ -115,6 +117,7 @@ "cid": "test_cid", "crid": "test_crid", "dealid": "test_dealid", + "mtype": 2, "ext": { "prebid": { "type": "video" diff --git a/adapters/adprime/adprimetest/exemplary/simple-web-banner.json b/adapters/adprime/adprimetest/exemplary/simple-web-banner.json index 74797b199b6..0af088bbdad 100644 --- a/adapters/adprime/adprimetest/exemplary/simple-web-banner.json +++ b/adapters/adprime/adprimetest/exemplary/simple-web-banner.json @@ -72,7 +72,8 @@ "ip": "123.123.123.123", "ua": "Ubuntu" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -91,6 +92,7 @@ "dealid": "test_dealid", "w": 468, "h": 60, + "mtype": 1, "ext": { "prebid": { "type": "banner" @@ -120,6 +122,7 @@ "dealid": "test_dealid", "w": 468, "h": 60, + "mtype": 1, "ext": { "prebid": { "type": "banner" diff --git a/adapters/adprime/adprimetest/exemplary/withAudiences.json b/adapters/adprime/adprimetest/exemplary/withAudiences.json index ed8e1c003b3..0a8ee04ddc8 100644 --- a/adapters/adprime/adprimetest/exemplary/withAudiences.json +++ b/adapters/adprime/adprimetest/exemplary/withAudiences.json @@ -76,7 +76,8 @@ "ip": "123.123.123.123", "ua": "iPad" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -95,6 +96,7 @@ "dealid": "test_dealid", "w": 300, "h": 250, + "mtype": 1, "ext": { "prebid": { "type": "banner" @@ -124,6 +126,7 @@ "dealid": "test_dealid", "w": 300, "h": 250, + "mtype": 1, "ext": { "prebid": { "type": "banner" diff --git a/adapters/adprime/adprimetest/exemplary/withKeywords.json b/adapters/adprime/adprimetest/exemplary/withKeywords.json index 3e1528fe78e..b18e3e2399f 100644 --- a/adapters/adprime/adprimetest/exemplary/withKeywords.json +++ b/adapters/adprime/adprimetest/exemplary/withKeywords.json @@ -74,7 +74,8 @@ "ip": "123.123.123.123", "ua": "iPad" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -93,6 +94,7 @@ "dealid": "test_dealid", "w": 300, "h": 250, + "mtype": 1, "ext": { "prebid": { "type": "banner" @@ -122,6 +124,7 @@ "dealid": "test_dealid", "w": 300, "h": 250, + "mtype": 1, "ext": { "prebid": { "type": "banner" diff --git a/adapters/adprime/adprimetest/supplemental/bad-imp-ext.json b/adapters/adprime/adprimetest/supplemental/bad-imp-ext.json index 39cd3cd02ce..d2ce5d82cb2 100644 --- a/adapters/adprime/adprimetest/supplemental/bad-imp-ext.json +++ b/adapters/adprime/adprimetest/supplemental/bad-imp-ext.json @@ -35,8 +35,8 @@ }, "expectedMakeRequestsErrors": [ { - "value": "unexpected end of JSON input", - "comparison": "literal" + "value": "expect { or n, but found", + "comparison": "startswith" } ] } \ No newline at end of file diff --git a/adapters/adprime/adprimetest/supplemental/bad_media_type.json b/adapters/adprime/adprimetest/supplemental/bad_media_type.json index d58833702f5..e66fb9965d6 100644 --- a/adapters/adprime/adprimetest/supplemental/bad_media_type.json +++ b/adapters/adprime/adprimetest/supplemental/bad_media_type.json @@ -48,7 +48,8 @@ "ip": "123.123.123.123", "ua": "Ubuntu" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -85,7 +86,7 @@ "expectedBidResponses": [{"currency":"USD","bids":[]}], "expectedMakeBidsErrors": [ { - "value": "Failed to find impression \"test-imp-id\"", + "value": "Unable to fetch mediaType in multi-format: test-imp-id", "comparison": "literal" } ] diff --git a/adapters/adprime/adprimetest/supplemental/bad_response.json b/adapters/adprime/adprimetest/supplemental/bad_response.json index acec885f98a..9893a1a6684 100644 --- a/adapters/adprime/adprimetest/supplemental/bad_response.json +++ b/adapters/adprime/adprimetest/supplemental/bad_response.json @@ -71,7 +71,8 @@ "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -80,7 +81,7 @@ }], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/adprime/adprimetest/supplemental/no-imp-ext-1.json b/adapters/adprime/adprimetest/supplemental/no-imp-ext-1.json index 5ba65925a70..ef5c3009a34 100644 --- a/adapters/adprime/adprimetest/supplemental/no-imp-ext-1.json +++ b/adapters/adprime/adprimetest/supplemental/no-imp-ext-1.json @@ -31,7 +31,7 @@ }, "expectedMakeRequestsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/adprime/adprimetest/supplemental/no-imp-ext-2.json b/adapters/adprime/adprimetest/supplemental/no-imp-ext-2.json index e08f5385b88..f9378c48881 100644 --- a/adapters/adprime/adprimetest/supplemental/no-imp-ext-2.json +++ b/adapters/adprime/adprimetest/supplemental/no-imp-ext-2.json @@ -31,8 +31,8 @@ }, "expectedMakeRequestsErrors": [ { - "value": "unexpected end of JSON input", - "comparison": "literal" + "value": "expect { or n, but found", + "comparison": "startswith" } ] } \ No newline at end of file diff --git a/adapters/adprime/adprimetest/supplemental/status-204.json b/adapters/adprime/adprimetest/supplemental/status-204.json index c0c2f346fb7..ca1a52b1031 100644 --- a/adapters/adprime/adprimetest/supplemental/status-204.json +++ b/adapters/adprime/adprimetest/supplemental/status-204.json @@ -71,7 +71,8 @@ "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/adprime/adprimetest/supplemental/status-400.json b/adapters/adprime/adprimetest/supplemental/status-400.json index ab1c38a0346..78e76ea9919 100644 --- a/adapters/adprime/adprimetest/supplemental/status-400.json +++ b/adapters/adprime/adprimetest/supplemental/status-400.json @@ -71,7 +71,8 @@ "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/adprime/adprimetest/supplemental/status-404.json b/adapters/adprime/adprimetest/supplemental/status-404.json index 6cd718548d3..fe3a247a63b 100644 --- a/adapters/adprime/adprimetest/supplemental/status-404.json +++ b/adapters/adprime/adprimetest/supplemental/status-404.json @@ -71,7 +71,8 @@ "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 404, diff --git a/adapters/adprime/params_test.go b/adapters/adprime/params_test.go index b466c658ede..18556d990ec 100644 --- a/adapters/adprime/params_test.go +++ b/adapters/adprime/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // TestValidParams makes sure that the adprime schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/adquery/adquery.go b/adapters/adquery/adquery.go index 6a7dafa0ccb..41813700058 100644 --- a/adapters/adquery/adquery.go +++ b/adapters/adquery/adquery.go @@ -8,11 +8,12 @@ import ( "strconv" "strings" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) const ( @@ -34,17 +35,14 @@ func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) ( } func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - headers.Add("Accept", "application/json") - headers.Add("x-openrtb-version", "2.5") + headers := buildHeaders(request) var result []*adapters.RequestData var errs []error for _, imp := range request.Imp { ext, err := parseExt(imp.Ext) if err != nil { - errs = append(errs, &errortypes.BadInput{err.Error()}) + errs = append(errs, &errortypes.BadInput{Message: err.Error()}) continue } @@ -58,6 +56,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRe Uri: a.endpoint, Body: requestJSON, Headers: headers, + ImpIDs: []string{imp.ID}, } result = append(result, data) } @@ -112,13 +111,27 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData return bidResponse, nil } +func buildHeaders(bidReq *openrtb2.BidRequest) http.Header { + headers := http.Header{} + + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("X-Openrtb-Version", "2.5") + + if bidReq.Device != nil && len(bidReq.Device.IP) > 0 { + headers.Add("X-Forwarded-For", bidReq.Device.IP) + } + + return headers +} + func buildRequest(bidReq *openrtb2.BidRequest, imp *openrtb2.Imp, ext *openrtb_ext.ImpExtAdQuery) *BidderRequest { userId := "" if bidReq.User != nil { userId = bidReq.User.ID } - return &BidderRequest{ + bidderRequest := &BidderRequest{ V: prebidVersion, PlacementCode: ext.PlacementID, AuctionId: "", @@ -132,17 +145,29 @@ func buildRequest(bidReq *openrtb2.BidRequest, imp *openrtb2.Imp, ext *openrtb_e BidderRequestsCount: 1, Sizes: getImpSizes(imp), } + + if bidReq.Device != nil { + bidderRequest.BidIp = bidReq.Device.IP + bidderRequest.BidIpv6 = bidReq.Device.IPv6 + bidderRequest.BidUa = bidReq.Device.UA + } + + if bidReq.Site != nil { + bidderRequest.BidPageUrl = bidReq.Site.Page + } + + return bidderRequest } func parseExt(ext json.RawMessage) (*openrtb_ext.ImpExtAdQuery, error) { var bext adapters.ExtImpBidder - err := json.Unmarshal(ext, &bext) + err := jsonutil.Unmarshal(ext, &bext) if err != nil { return nil, err } var adsExt openrtb_ext.ImpExtAdQuery - err = json.Unmarshal(bext.Bidder, &adsExt) + err = jsonutil.Unmarshal(bext.Bidder, &adsExt) if err != nil { return nil, err } @@ -153,7 +178,7 @@ func parseExt(ext json.RawMessage) (*openrtb_ext.ImpExtAdQuery, error) { func parseResponseJson(respBody []byte) (*ResponseData, float64, int64, int64, []error) { var response ResponseAdQuery - if err := json.Unmarshal(respBody, &response); err != nil { + if err := jsonutil.Unmarshal(respBody, &response); err != nil { return nil, 0, 0, 0, []error{err} } diff --git a/adapters/adquery/adquery_test.go b/adapters/adquery/adquery_test.go index 228d835d6c4..913e5781950 100644 --- a/adapters/adquery/adquery_test.go +++ b/adapters/adquery/adquery_test.go @@ -1,15 +1,16 @@ package adquery import ( - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdquery, config.Adapter{ - Endpoint: "https://bidder.adquery.io/prebid/bid"}, + Endpoint: "https://bidder2.adquery.io/prebid/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 902, DataCenter: "2"}) if buildErr != nil { diff --git a/adapters/adquery/adquerytest/exemplary/empty.json b/adapters/adquery/adquerytest/exemplary/empty.json index 04f1d21bcab..055d9829e63 100644 --- a/adapters/adquery/adquerytest/exemplary/empty.json +++ b/adapters/adquery/adquerytest/exemplary/empty.json @@ -4,9 +4,16 @@ "user": { "id": "xyz" }, + "site": { + "page": "https://www.example.com" + }, + "device": { + "ip": "104.28.131.104", + "ua": "PostmanRuntime/7.26.8" + }, "imp": [], "bidder": "adquery" }, "httpCalls": [], "expectedBidResponses": [] -} \ No newline at end of file +} diff --git a/adapters/adquery/adquerytest/exemplary/many-imps.json b/adapters/adquery/adquerytest/exemplary/many-imps.json index a3985c87dc1..03747b5dd09 100644 --- a/adapters/adquery/adquerytest/exemplary/many-imps.json +++ b/adapters/adquery/adquerytest/exemplary/many-imps.json @@ -4,6 +4,13 @@ "user": { "id": "d93f2a0e5f0fe2cc3a6e" }, + "site": { + "page": "https://www.example.com" + }, + "device": { + "ip": "104.28.131.104", + "ua": "PostmanRuntime/7.26.8" + }, "imp": [ { "id": "1", @@ -27,7 +34,8 @@ "type": "banner" } } - },{ + }, + { "id": "2", "tagid": "test-banner-imp-id-2", "bidder": "adquery", @@ -52,26 +60,31 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], - "X-Openrtb-Version": ["2.5"] + "X-Openrtb-Version": ["2.5"], + "X-Forwarded-For": ["104.28.131.104"] }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "104.28.131.104", + "bidIpv6": "", + "bidPageUrl": "https://www.example.com", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "PostmanRuntime/7.26.8", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", "v": "server" - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 200, @@ -98,9 +111,7 @@ "urlAdq": "https://adquery.io", "creationId": 7211, "currency": "EUR", - "adDomains": [ - "https://s1.adquery.io" - ], + "adDomains": ["https://s1.adquery.io"], "tag": " ", "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", "mediaType": { @@ -117,28 +128,34 @@ } } } - },{ + }, + { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], - "X-Openrtb-Version": ["2.5"] + "X-Openrtb-Version": ["2.5"], + "X-Forwarded-For": ["104.28.131.104"] }, "body": { "adUnitCode": "test-banner-imp-id-2", - "bidId": "22e26bd9a702bc2", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc2", + "bidIp": "104.28.131.104", + "bidIpv6": "", + "bidPageUrl": "https://www.example.com", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "PostmanRuntime/7.26.8", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f898", "sizes": "300x250", "type": "banner", "v": "server" - } + }, + "impIDs":["2"] }, "mockResponse": { "status": 200, @@ -165,9 +182,7 @@ "urlAdq": "https://adquery.io", "creationId": 7211, "currency": "EUR", - "adDomains": [ - "https://s1.adquery.io" - ], + "adDomains": ["https://s1.adquery.io"], "tag": " ", "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", "mediaType": { @@ -224,4 +239,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/adapters/adquery/adquerytest/exemplary/no-currency.json b/adapters/adquery/adquerytest/exemplary/no-currency.json index e97e4b9beaa..a40d032eaf8 100644 --- a/adapters/adquery/adquerytest/exemplary/no-currency.json +++ b/adapters/adquery/adquerytest/exemplary/no-currency.json @@ -4,6 +4,13 @@ "user": { "id": "d93f2a0e5f0fe2cc3a6e" }, + "site": { + "page": "https://www.example.com" + }, + "device": { + "ip": "104.28.131.104", + "ua": "PostmanRuntime/7.26.8" + }, "imp": [ { "id": "1", @@ -22,10 +29,10 @@ ] }, "ext": { - "bidder": { - "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", - "type": "banner" - } + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } } } ], @@ -34,26 +41,31 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], - "X-Openrtb-Version": ["2.5"] + "X-Openrtb-Version": ["2.5"], + "X-Forwarded-For": ["104.28.131.104"] }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "104.28.131.104", + "bidIpv6": "", + "bidPageUrl": "https://www.example.com", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "PostmanRuntime/7.26.8", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", "v": "server" - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 200, @@ -79,9 +91,7 @@ "domain": "https://bidder.adquery.io", "urlAdq": "https://adquery.io", "creationId": 7211, - "adDomains": [ - "https://s1.adquery.io" - ], + "adDomains": ["https://s1.adquery.io"], "tag": " ", "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", "mediaType": { @@ -120,4 +130,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/adapters/adquery/adquerytest/exemplary/ok.json b/adapters/adquery/adquerytest/exemplary/ok.json index e725e055293..49ab3253842 100644 --- a/adapters/adquery/adquerytest/exemplary/ok.json +++ b/adapters/adquery/adquerytest/exemplary/ok.json @@ -4,6 +4,14 @@ "user": { "id": "d93f2a0e5f0fe2cc3a6e" }, + "site": { + "page": "https://www.example.com" + }, + "device": { + "ip": "104.28.131.104", + "ipv6": "2001:4860:4860::8888", + "ua": "PostmanRuntime/7.26.8" + }, "imp": [ { "id": "1", @@ -22,10 +30,10 @@ ] }, "ext": { - "bidder": { - "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", - "type": "banner" - } + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } } } ], @@ -34,26 +42,31 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], - "X-Openrtb-Version": ["2.5"] + "X-Openrtb-Version": ["2.5"], + "X-Forwarded-For": ["104.28.131.104"] }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "104.28.131.104", + "bidIpv6": "2001:4860:4860::8888", + "bidPageUrl": "https://www.example.com", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "PostmanRuntime/7.26.8", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", "v": "server" - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 200, @@ -80,9 +93,7 @@ "urlAdq": "https://adquery.io", "creationId": 7211, "currency": "EUR", - "adDomains": [ - "https://s1.adquery.io" - ], + "adDomains": ["https://s1.adquery.io"], "tag": " ", "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", "mediaType": { @@ -121,4 +132,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/adapters/adquery/adquerytest/exemplary/single-imp-banner-format.json b/adapters/adquery/adquerytest/exemplary/single-imp-banner-format.json index 7a0092d5f5a..538284c6967 100644 --- a/adapters/adquery/adquerytest/exemplary/single-imp-banner-format.json +++ b/adapters/adquery/adquerytest/exemplary/single-imp-banner-format.json @@ -4,6 +4,13 @@ "user": { "id": "d93f2a0e5f0fe2cc3a6e" }, + "site": { + "page": "https://www.example.com" + }, + "device": { + "ip": "104.28.131.104", + "ua": "PostmanRuntime/7.26.8" + }, "imp": [ { "id": "1", @@ -26,26 +33,31 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], - "X-Openrtb-Version": ["2.5"] + "X-Openrtb-Version": ["2.5"], + "X-Forwarded-For": ["104.28.131.104"] }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "104.28.131.104", + "bidIpv6": "", + "bidPageUrl": "https://www.example.com", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "PostmanRuntime/7.26.8", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100", "type": "banner", "v": "server" - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 200, @@ -72,9 +84,7 @@ "urlAdq": "https://adquery.io", "creationId": 7211, "currency": "PLN", - "adDomains": [ - "https://s1.adquery.io" - ], + "adDomains": ["https://s1.adquery.io"], "tag": " ", "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", "mediaType": { @@ -112,4 +122,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/adapters/adquery/adquerytest/supplemental/data-null.json b/adapters/adquery/adquerytest/supplemental/data-null.json index 0d21a2bd9f7..e61f997c0a2 100644 --- a/adapters/adquery/adquerytest/supplemental/data-null.json +++ b/adapters/adquery/adquerytest/supplemental/data-null.json @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -42,18 +42,22 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", "v": "server" - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 200, @@ -69,4 +73,4 @@ "bids": [] } ] -} \ No newline at end of file +} diff --git a/adapters/adquery/adquerytest/supplemental/invalid-numerical-values.json b/adapters/adquery/adquerytest/supplemental/invalid-numerical-values.json index daff3d0828c..66454799ff8 100644 --- a/adapters/adquery/adquerytest/supplemental/invalid-numerical-values.json +++ b/adapters/adquery/adquerytest/supplemental/invalid-numerical-values.json @@ -22,10 +22,10 @@ ] }, "ext": { - "bidder": { - "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", - "type": "banner" - } + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } } } ], @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -42,18 +42,22 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", "v": "server" - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 200, @@ -80,9 +84,7 @@ "urlAdq": "https://adquery.io", "creationId": 7211, "currency": "PLN", - "adDomains": [ - "https://s1.adquery.io" - ], + "adDomains": ["https://s1.adquery.io"], "tag": " ", "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", "mediaType": { @@ -101,14 +103,18 @@ } } ], - "expectedMakeBidsErrors": [{ - "value": "strconv.ParseFloat: parsing \"$4.14\": invalid syntax", - "comparison": "literal" - },{ - "value": "strconv.ParseInt: parsing \"320px\": invalid syntax", - "comparison": "literal" - },{ - "value": "strconv.ParseInt: parsing \"50px\": invalid syntax", - "comparison": "literal" - }] -} \ No newline at end of file + "expectedMakeBidsErrors": [ + { + "value": "strconv.ParseFloat: parsing \"$4.14\": invalid syntax", + "comparison": "literal" + }, + { + "value": "strconv.ParseInt: parsing \"320px\": invalid syntax", + "comparison": "literal" + }, + { + "value": "strconv.ParseInt: parsing \"50px\": invalid syntax", + "comparison": "literal" + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/malformed-ext.json b/adapters/adquery/adquerytest/supplemental/malformed-ext.json index 46aaaed431d..aa035c5852b 100644 --- a/adapters/adquery/adquerytest/supplemental/malformed-ext.json +++ b/adapters/adquery/adquerytest/supplemental/malformed-ext.json @@ -32,7 +32,7 @@ "bidder": "adquery" }, "expectedMakeRequestsErrors": [{ - "value": "json: cannot unmarshal array into Go struct field ImpExtAdQuery.placementId of type string", + "value": "cannot unmarshal openrtb_ext.ImpExtAdQuery.PlacementID: expects \" or n, but found [", "comparison": "literal" }] } \ No newline at end of file diff --git a/adapters/adquery/adquerytest/supplemental/malformed-resp.json b/adapters/adquery/adquerytest/supplemental/malformed-resp.json index f7aa271e6fe..f485f394c87 100644 --- a/adapters/adquery/adquerytest/supplemental/malformed-resp.json +++ b/adapters/adquery/adquerytest/supplemental/malformed-resp.json @@ -22,10 +22,10 @@ ] }, "ext": { - "bidder": { - "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", - "type": "banner" - } + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } } } ], @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -42,18 +42,22 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", "v": "server" - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 200, @@ -80,9 +84,7 @@ "urlAdq": "https://adquery.io", "creationId": "string-identifier", "currency": "PLN", - "adDomains": [ - "https://s1.adquery.io" - ], + "adDomains": ["https://s1.adquery.io"], "tag": " ", "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", "mediaType": { @@ -101,8 +103,10 @@ } } ], - "expectedMakeBidsErrors": [{ - "value": "json: cannot unmarshal string into Go struct field ResponseData.data.creationId of type int64", - "comparison": "literal" - }] -} \ No newline at end of file + "expectedMakeBidsErrors": [ + { + "value": "cannot unmarshal adquery.ResponseData.CrID: unexpected character", + "comparison": "startswith" + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/mediatype-unknown.json b/adapters/adquery/adquerytest/supplemental/mediatype-unknown.json index bf3cdc63f45..5e81bd87b01 100644 --- a/adapters/adquery/adquerytest/supplemental/mediatype-unknown.json +++ b/adapters/adquery/adquerytest/supplemental/mediatype-unknown.json @@ -22,10 +22,10 @@ ] }, "ext": { - "bidder": { - "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", - "type": "banner" - } + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } } } ], @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -42,18 +42,22 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", "v": "server" - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 200, @@ -80,9 +84,7 @@ "urlAdq": "https://adquery.io", "creationId": 7211, "currency": "PLN", - "adDomains": [ - "https://s1.adquery.io" - ], + "adDomains": ["https://s1.adquery.io"], "tag": " ", "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", "mediaType": { @@ -101,8 +103,10 @@ } } ], - "expectedMakeBidsErrors": [{ - "value": "unsupported MediaType: unknown", - "comparison": "literal" - }] -} \ No newline at end of file + "expectedMakeBidsErrors": [ + { + "value": "unsupported MediaType: unknown", + "comparison": "literal" + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/mediatype-video.json b/adapters/adquery/adquerytest/supplemental/mediatype-video.json index 7becebab291..40e7ed5baed 100644 --- a/adapters/adquery/adquerytest/supplemental/mediatype-video.json +++ b/adapters/adquery/adquerytest/supplemental/mediatype-video.json @@ -22,10 +22,10 @@ ] }, "ext": { - "bidder": { - "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", - "type": "banner" - } + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } } } ], @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -42,18 +42,22 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", "v": "server" - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 200, @@ -80,9 +84,7 @@ "urlAdq": "https://adquery.io", "creationId": 7211, "currency": "PLN", - "adDomains": [ - "https://s1.adquery.io" - ], + "adDomains": ["https://s1.adquery.io"], "tag": " ", "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", "mediaType": { @@ -101,8 +103,10 @@ } } ], - "expectedMakeBidsErrors": [{ - "value": "unsupported MediaType: video", - "comparison": "literal" - }] -} \ No newline at end of file + "expectedMakeBidsErrors": [ + { + "value": "unsupported MediaType: video", + "comparison": "literal" + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/no-device.json b/adapters/adquery/adquerytest/supplemental/no-device.json new file mode 100644 index 00000000000..d368072ff8f --- /dev/null +++ b/adapters/adquery/adquerytest/supplemental/no-device.json @@ -0,0 +1,129 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "site": { + "page": "http://www.example.com" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "http://www.example.com", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "320x100,300x250", + "type": "banner", + "v": "server" + }, + "impIDs":["1"] + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "data": { + "requestId": "22e26bd9a702bc1", + "emission_id": "22e26bd9a702bc1", + "eventTracker": "https://bidder.adquery.io/prebid/ev/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "externalEmissionCodes": "", + "impressionTracker": "https://bidder.adquery.io/prebid/im/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "viewabilityTracker": "https://bidder.adquery.io/prebid/vi/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "clickTracker": "https://bidder.adquery.io/prebid/cl/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?url=https%3A%2F%2Fbrodacid.pl%2F%3Futm_source%3Dmobile_open%26utm_medium%3Dcpc%26utm_campaign%3Dhi2023", + "link": "https://brodacid.pl/?utm_source=mobile_open&utm_medium=cpc&utm_campaign=hi2023", + "logo": "https://api.adquery.io/img/adquery.png", + "medias": [ + { + "src": "banner/2023-06-05/17591", + "ext": "zip", + "type": 3 + } + ], + "domain": "https://bidder.adquery.io", + "urlAdq": "https://adquery.io", + "creationId": 7211, + "currency": "EUR", + "adDomains": ["https://s1.adquery.io"], + "tag": " ", + "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", + "mediaType": { + "width": "320", + "height": "50", + "name": "banner", + "type": "banner320x50" + }, + "cpm": "4.14", + "qid": "fc08aacb07eac44421ed", + "width": "320", + "height": "50", + "isExpand": false + } + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "22e26bd9a702bc1", + "impid": "1", + "price": 4.14, + "adm": " ", + "adomain": ["https://s1.adquery.io"], + "crid": "7211", + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/no-imp-banner-measures.json b/adapters/adquery/adquerytest/supplemental/no-imp-banner-measures.json index 953d6de5d8d..58219cc9419 100644 --- a/adapters/adquery/adquerytest/supplemental/no-imp-banner-measures.json +++ b/adapters/adquery/adquerytest/supplemental/no-imp-banner-measures.json @@ -23,7 +23,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -31,18 +31,22 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "", "type": "banner", "v": "server" - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 200, @@ -58,4 +62,4 @@ "bids": [] } ] -} \ No newline at end of file +} diff --git a/adapters/adquery/adquerytest/supplemental/no-imp-banner.json b/adapters/adquery/adquerytest/supplemental/no-imp-banner.json index 8589d97f606..dcdfd9e9351 100644 --- a/adapters/adquery/adquerytest/supplemental/no-imp-banner.json +++ b/adapters/adquery/adquerytest/supplemental/no-imp-banner.json @@ -22,7 +22,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -30,18 +30,22 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "", "type": "banner", "v": "server" - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 200, @@ -57,4 +61,4 @@ "bids": [] } ] -} \ No newline at end of file +} diff --git a/adapters/adquery/adquerytest/supplemental/no-site.json b/adapters/adquery/adquerytest/supplemental/no-site.json new file mode 100644 index 00000000000..500dc87b356 --- /dev/null +++ b/adapters/adquery/adquerytest/supplemental/no-site.json @@ -0,0 +1,131 @@ +{ + "mockBidRequest": { + "id": "22e26bd9a702bc", + "user": { + "id": "d93f2a0e5f0fe2cc3a6e" + }, + "device": { + "ip": "104.28.131.104", + "ua": "PostmanRuntime/7.26.8" + }, + "imp": [ + { + "id": "1", + "tagid": "test-banner-imp-id", + "bidder": "adquery", + "banner": { + "format": [ + { + "w": 320, + "h": 100 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } + } + } + ], + "bidder": "adquery" + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder2.adquery.io/prebid/bid", + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"], + "X-Openrtb-Version": ["2.5"], + "X-Forwarded-For": ["104.28.131.104"] + }, + "body": { + "adUnitCode": "test-banner-imp-id", + "bidder": "adquery", + "bidderRequestId": "22e26bd9a702bc", + "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "104.28.131.104", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "PostmanRuntime/7.26.8", + "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "sizes": "320x100,300x250", + "type": "banner", + "v": "server" + }, + "impIDs":["1"] + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "data": { + "requestId": "22e26bd9a702bc1", + "emission_id": "22e26bd9a702bc1", + "eventTracker": "https://bidder.adquery.io/prebid/ev/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "externalEmissionCodes": "", + "impressionTracker": "https://bidder.adquery.io/prebid/im/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "viewabilityTracker": "https://bidder.adquery.io/prebid/vi/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?ad=1", + "clickTracker": "https://bidder.adquery.io/prebid/cl/d30f79cf7fef47bd7a5611719f936539bec0d2e9/22e26bd9a702bc?url=https%3A%2F%2Fbrodacid.pl%2F%3Futm_source%3Dmobile_open%26utm_medium%3Dcpc%26utm_campaign%3Dhi2023", + "link": "https://brodacid.pl/?utm_source=mobile_open&utm_medium=cpc&utm_campaign=hi2023", + "logo": "https://api.adquery.io/img/adquery.png", + "medias": [ + { + "src": "banner/2023-06-05/17591", + "ext": "zip", + "type": 3 + } + ], + "domain": "https://bidder.adquery.io", + "urlAdq": "https://adquery.io", + "creationId": 7211, + "currency": "EUR", + "adDomains": ["https://s1.adquery.io"], + "tag": " ", + "adqLib": "https://api.adquery.io/js/adquery-0.1.js?time=10", + "mediaType": { + "width": "320", + "height": "50", + "name": "banner", + "type": "banner320x50" + }, + "cpm": "4.14", + "qid": "fc08aacb07eac44421ed", + "width": "320", + "height": "50", + "isExpand": false + } + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "22e26bd9a702bc1", + "impid": "1", + "price": 4.14, + "adm": " ", + "adomain": ["https://s1.adquery.io"], + "crid": "7211", + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/resp-bad-request.json b/adapters/adquery/adquerytest/supplemental/resp-bad-request.json index cb869625720..92a3748e81a 100644 --- a/adapters/adquery/adquerytest/supplemental/resp-bad-request.json +++ b/adapters/adquery/adquerytest/supplemental/resp-bad-request.json @@ -22,10 +22,10 @@ ] }, "ext": { - "bidder": { - "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", - "type": "banner" - } + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } } } ], @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -42,18 +42,22 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", "v": "server" - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 400, @@ -61,8 +65,10 @@ } } ], - "expectedMakeBidsErrors": [{ - "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", - "comparison": "literal" - }] -} \ No newline at end of file + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/adquery/adquerytest/supplemental/resp-no-content.json b/adapters/adquery/adquerytest/supplemental/resp-no-content.json index 5817e15a533..f79df18b450 100644 --- a/adapters/adquery/adquerytest/supplemental/resp-no-content.json +++ b/adapters/adquery/adquerytest/supplemental/resp-no-content.json @@ -22,10 +22,10 @@ ] }, "ext": { - "bidder": { - "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", - "type": "banner" - } + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } } } ], @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -42,18 +42,22 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", "v": "server" - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 204, @@ -61,6 +65,5 @@ } } ], - "expectedBidResponses": [ - ] -} \ No newline at end of file + "expectedBidResponses": [] +} diff --git a/adapters/adquery/adquerytest/supplemental/resp-server-error.json b/adapters/adquery/adquerytest/supplemental/resp-server-error.json index 05c1cae8488..fc0cbd8c9ab 100644 --- a/adapters/adquery/adquerytest/supplemental/resp-server-error.json +++ b/adapters/adquery/adquerytest/supplemental/resp-server-error.json @@ -22,10 +22,10 @@ ] }, "ext": { - "bidder": { - "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", - "type": "banner" - } + "bidder": { + "placementId": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", + "type": "banner" + } } } ], @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://bidder.adquery.io/prebid/bid", + "uri": "https://bidder2.adquery.io/prebid/bid", "headers": { "Accept": ["application/json"], "Content-Type": ["application/json;charset=utf-8"], @@ -42,18 +42,22 @@ }, "body": { "adUnitCode": "test-banner-imp-id", - "bidId": "22e26bd9a702bc1", - "bidQid": "d93f2a0e5f0fe2cc3a6e", - "bidPageUrl": "", "bidder": "adquery", "bidderRequestId": "22e26bd9a702bc", - "bidRequestsCount": 1, "bidderRequestsCount": 1, + "bidId": "22e26bd9a702bc1", + "bidIp": "", + "bidIpv6": "", + "bidPageUrl": "", + "bidQid": "d93f2a0e5f0fe2cc3a6e", + "bidRequestsCount": 1, + "bidUa": "", "placementCode": "6d93f2a0e5f0fe2cc3a6e9e3ade964b43b07f897", "sizes": "320x100,300x250", "type": "banner", "v": "server" - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 500, @@ -61,8 +65,10 @@ } } ], - "expectedMakeBidsErrors": [{ - "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", - "comparison": "literal" - }] -} \ No newline at end of file + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/adquery/params_test.go b/adapters/adquery/params_test.go index cba021007d3..5619f59cb5e 100644 --- a/adapters/adquery/params_test.go +++ b/adapters/adquery/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/adquery/types.go b/adapters/adquery/types.go index e46afaea63e..62692bb0d02 100644 --- a/adapters/adquery/types.go +++ b/adapters/adquery/types.go @@ -1,6 +1,6 @@ package adquery -import "github.com/prebid/prebid-server/openrtb_ext" +import "github.com/prebid/prebid-server/v3/openrtb_ext" type BidderRequest struct { V string `json:"v"` @@ -10,6 +10,9 @@ type BidderRequest struct { AdUnitCode string `json:"adUnitCode"` BidQid string `json:"bidQid"` BidId string `json:"bidId"` + BidIp string `json:"bidIp"` + BidIpv6 string `json:"bidIpv6"` + BidUa string `json:"bidUa"` Bidder string `json:"bidder"` BidPageUrl string `json:"bidPageUrl"` BidderRequestId string `json:"bidderRequestId"` diff --git a/adapters/adrino/adrino.go b/adapters/adrino/adrino.go index a63ad9beef6..9139d01789a 100644 --- a/adapters/adrino/adrino.go +++ b/adapters/adrino/adrino.go @@ -5,11 +5,12 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { @@ -33,6 +34,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E Uri: a.endpoint, Body: reqJson, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }}, errs } @@ -57,7 +59,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } diff --git a/adapters/adrino/adrino_test.go b/adapters/adrino/adrino_test.go index 7566f3ed499..48875bed7cc 100644 --- a/adapters/adrino/adrino_test.go +++ b/adapters/adrino/adrino_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adrino/adrinotest/exemplary/no-bid.json b/adapters/adrino/adrinotest/exemplary/no-bid.json index 66b64e14690..54189a50d87 100644 --- a/adapters/adrino/adrinotest/exemplary/no-bid.json +++ b/adapters/adrino/adrinotest/exemplary/no-bid.json @@ -61,7 +61,8 @@ "w": 1920, "h": 800 } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/adrino/adrinotest/exemplary/single-native.json b/adapters/adrino/adrinotest/exemplary/single-native.json index 41e4b5ef3ef..25fb1c9d3fb 100644 --- a/adapters/adrino/adrinotest/exemplary/single-native.json +++ b/adapters/adrino/adrinotest/exemplary/single-native.json @@ -61,7 +61,8 @@ "w": 1920, "h": 800 } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adrino/adrinotest/supplemental/unknown-hash.json b/adapters/adrino/adrinotest/supplemental/unknown-hash.json index e678b5267b1..b60f082baaf 100644 --- a/adapters/adrino/adrinotest/supplemental/unknown-hash.json +++ b/adapters/adrino/adrinotest/supplemental/unknown-hash.json @@ -61,7 +61,8 @@ "w": 1920, "h": 800 } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/adrino/params_test.go b/adapters/adrino/params_test.go index f82f08ce9e0..a160113fbd1 100644 --- a/adapters/adrino/params_test.go +++ b/adapters/adrino/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // This file actually intends to test static/bidder-params/adrino.json diff --git a/adapters/adsinteractive/adsinteractive.go b/adapters/adsinteractive/adsinteractive.go index 04edf774b80..1969ceb7cc9 100644 --- a/adapters/adsinteractive/adsinteractive.go +++ b/adapters/adsinteractive/adsinteractive.go @@ -5,11 +5,12 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { @@ -34,6 +35,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte Method: "POST", Uri: a.endpoint, Body: requestJSON, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), } return []*adapters.RequestData{requestData}, nil @@ -59,7 +61,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R } var response openrtb2.BidResponse - if err := json.Unmarshal(responseData.Body, &response); err != nil { + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { return nil, []error{err} } diff --git a/adapters/adsinteractive/adsinteractive_test.go b/adapters/adsinteractive/adsinteractive_test.go index 9a1397b799f..ae83c615fe8 100644 --- a/adapters/adsinteractive/adsinteractive_test.go +++ b/adapters/adsinteractive/adsinteractive_test.go @@ -3,9 +3,9 @@ package adsinteractive import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) const testsDir = "adsinteractivetest" diff --git a/adapters/adsinteractive/adsinteractivetest/exemplary/banner.json b/adapters/adsinteractive/adsinteractivetest/exemplary/banner.json index 53b96fbe0f4..bc4d4c70913 100644 --- a/adapters/adsinteractive/adsinteractivetest/exemplary/banner.json +++ b/adapters/adsinteractive/adsinteractivetest/exemplary/banner.json @@ -35,7 +35,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adsinteractive/adsinteractivetest/supplemental/bad-request-400.json b/adapters/adsinteractive/adsinteractivetest/supplemental/bad-request-400.json index 695f2fb0c5e..d584c81144c 100644 --- a/adapters/adsinteractive/adsinteractivetest/supplemental/bad-request-400.json +++ b/adapters/adsinteractive/adsinteractivetest/supplemental/bad-request-400.json @@ -46,7 +46,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/adsinteractive/adsinteractivetest/supplemental/bad-response.json b/adapters/adsinteractive/adsinteractivetest/supplemental/bad-response.json index 98acce17a91..3f40cfa3512 100644 --- a/adapters/adsinteractive/adsinteractivetest/supplemental/bad-response.json +++ b/adapters/adsinteractive/adsinteractivetest/supplemental/bad-response.json @@ -45,7 +45,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -56,7 +57,7 @@ "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/adsinteractive/adsinteractivetest/supplemental/no-content-204.json b/adapters/adsinteractive/adsinteractivetest/supplemental/no-content-204.json index 9b24dca0308..ee5adb91939 100644 --- a/adapters/adsinteractive/adsinteractivetest/supplemental/no-content-204.json +++ b/adapters/adsinteractive/adsinteractivetest/supplemental/no-content-204.json @@ -43,7 +43,8 @@ } } ] - } + }, + "impIDs":["fake-imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/adsinteractive/adsinteractivetest/supplemental/service-unavailable-503.json b/adapters/adsinteractive/adsinteractivetest/supplemental/service-unavailable-503.json index 42e259b8db8..2800076417f 100644 --- a/adapters/adsinteractive/adsinteractivetest/supplemental/service-unavailable-503.json +++ b/adapters/adsinteractive/adsinteractivetest/supplemental/service-unavailable-503.json @@ -45,7 +45,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 503, diff --git a/adapters/adsinteractive/params_test.go b/adapters/adsinteractive/params_test.go index 2561fc864da..6b377d6aa36 100644 --- a/adapters/adsinteractive/params_test.go +++ b/adapters/adsinteractive/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // This file actually intends to test static/bidder-params/adsinteractive.json diff --git a/adapters/adtarget/adtarget.go b/adapters/adtarget/adtarget.go index 00f797eccf8..c78ffe830f6 100644 --- a/adapters/adtarget/adtarget.go +++ b/adapters/adtarget/adtarget.go @@ -5,11 +5,12 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type AdtargetAdapter struct { @@ -74,6 +75,7 @@ func (a *AdtargetAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad Uri: a.endpoint + fmt.Sprintf("?aid=%d", sourceId), Body: body, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }) } @@ -91,7 +93,7 @@ func (a *AdtargetAdapter) MakeBids(bidReq *openrtb2.BidRequest, unused *adapters }} } var bidResp openrtb2.BidResponse - if err := json.Unmarshal(httpRes.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(httpRes.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("error while decoding response, err: %s", err), }} @@ -153,14 +155,14 @@ func validateImpressionAndSetExt(imp *openrtb2.Imp) (int, error) { var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { return 0, &errortypes.BadInput{ Message: fmt.Sprintf("ignoring imp id=%s, error while decoding extImpBidder, err: %s", imp.ID, err), } } impExt := openrtb_ext.ExtImpAdtarget{} - err := json.Unmarshal(bidderExt.Bidder, &impExt) + err := jsonutil.Unmarshal(bidderExt.Bidder, &impExt) if err != nil { return 0, &errortypes.BadInput{ Message: fmt.Sprintf("ignoring imp id=%s, error while decoding impExt, err: %s", imp.ID, err), @@ -173,14 +175,24 @@ func validateImpressionAndSetExt(imp *openrtb2.Imp) (int, error) { impExtBuffer, err = json.Marshal(&adtargetImpExt{ Adtarget: impExt, }) - + if err != nil { + return 0, &errortypes.BadInput{ + Message: fmt.Sprintf("ignoring imp id=%s, error while encoding impExt, err: %s", imp.ID, err), + } + } if impExt.BidFloor > 0 { imp.BidFloor = impExt.BidFloor } imp.Ext = impExtBuffer - return impExt.SourceId, nil + aid, err := impExt.SourceId.Int64() + if err != nil { + return 0, &errortypes.BadInput{ + Message: fmt.Sprintf("ignoring imp id=%s, aid parsing err: %s", imp.ID, err), + } + } + return int(aid), nil } // Builder builds a new instance of the Adtarget adapter for the given bidder with the given config. diff --git a/adapters/adtarget/adtarget_test.go b/adapters/adtarget/adtarget_test.go index 2ee45041b09..23cb51fdfad 100644 --- a/adapters/adtarget/adtarget_test.go +++ b/adapters/adtarget/adtarget_test.go @@ -3,9 +3,9 @@ package adtarget import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adtarget/adtargettest/exemplary/media-type-mapping.json b/adapters/adtarget/adtargettest/exemplary/media-type-mapping.json index 518268d4fea..acc8c850931 100644 --- a/adapters/adtarget/adtargettest/exemplary/media-type-mapping.json +++ b/adapters/adtarget/adtargettest/exemplary/media-type-mapping.json @@ -45,7 +45,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adtarget/adtargettest/exemplary/simple-banner.json b/adapters/adtarget/adtargettest/exemplary/simple-banner.json index 0d925bacd9f..2745b5332b9 100644 --- a/adapters/adtarget/adtargettest/exemplary/simple-banner.json +++ b/adapters/adtarget/adtargettest/exemplary/simple-banner.json @@ -52,7 +52,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/adtarget/adtargettest/exemplary/simple-video.json b/adapters/adtarget/adtargettest/exemplary/simple-video.json index 5b47751749c..fdfef91de41 100644 --- a/adapters/adtarget/adtargettest/exemplary/simple-video.json +++ b/adapters/adtarget/adtargettest/exemplary/simple-video.json @@ -45,7 +45,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/adtarget/adtargettest/supplemental/explicit-dimensions.json b/adapters/adtarget/adtargettest/supplemental/explicit-dimensions.json index c33b1bb2daa..10496adaea0 100644 --- a/adapters/adtarget/adtargettest/supplemental/explicit-dimensions.json +++ b/adapters/adtarget/adtargettest/supplemental/explicit-dimensions.json @@ -48,7 +48,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/adtarget/adtargettest/supplemental/wrong-impression-ext.json b/adapters/adtarget/adtargettest/supplemental/wrong-impression-ext.json index 1986dfaf13f..7f2b1e3cfcb 100644 --- a/adapters/adtarget/adtargettest/supplemental/wrong-impression-ext.json +++ b/adapters/adtarget/adtargettest/supplemental/wrong-impression-ext.json @@ -19,7 +19,7 @@ "expectedMakeRequestsErrors": [ { - "value": "ignoring imp id=unsupported-native-imp, error while decoding impExt, err: json: cannot unmarshal string into Go struct field ExtImpAdtarget.aid of type int", + "value": "ignoring imp id=unsupported-native-imp, error while encoding impExt, err: json: invalid number literal \"some string instead of int\"", "comparison": "literal" } ] diff --git a/adapters/adtarget/adtargettest/supplemental/wrong-impression-mapping.json b/adapters/adtarget/adtargettest/supplemental/wrong-impression-mapping.json index 2aac06a9e68..da594aa74dd 100644 --- a/adapters/adtarget/adtargettest/supplemental/wrong-impression-mapping.json +++ b/adapters/adtarget/adtargettest/supplemental/wrong-impression-mapping.json @@ -45,7 +45,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adtarget/params_test.go b/adapters/adtarget/params_test.go index 4c39639fb7b..cb5a8c94791 100644 --- a/adapters/adtarget/params_test.go +++ b/adapters/adtarget/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // This file actually intends to test static/bidder-params/adtarget.json @@ -33,16 +33,21 @@ func TestInvalidParams(t *testing.T) { for _, invalidParam := range invalidParams { if err := validator.Validate(openrtb_ext.BidderAdtarget, json.RawMessage(invalidParam)); err == nil { - t.Errorf("Schema allowed unexpected params: %s", invalidParam) + ext := openrtb_ext.ExtImpAdtarget{} + err = json.Unmarshal([]byte(invalidParam), &ext) + if err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } } } } var validParams = []string{ `{"aid":123}`, + `{"aid":"123"}`, `{"aid":123,"placementId":1234}`, `{"aid":123,"siteId":4321}`, - `{"aid":123,"siteId":0,"bidFloor":0}`, + `{"aid":"123","siteId":0,"bidFloor":0}`, } var invalidParams = []string{ @@ -53,8 +58,7 @@ var invalidParams = []string{ `4.2`, `[]`, `{}`, - `{"aid":"123"}`, - `{"aid":"0"}`, + `{"aid":"qwerty"}`, `{"aid":"123","placementId":"123"}`, `{"aid":123, "placementId":"123", "siteId":"321"}`, } diff --git a/adapters/adtelligent/adtelligent.go b/adapters/adtelligent/adtelligent.go index e2f5ef82cab..f56a39736a3 100644 --- a/adapters/adtelligent/adtelligent.go +++ b/adapters/adtelligent/adtelligent.go @@ -5,11 +5,12 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type AdtelligentAdapter struct { @@ -74,6 +75,7 @@ func (a *AdtelligentAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo Uri: a.endpoint + fmt.Sprintf("?aid=%d", sourceId), Body: body, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }) } @@ -92,7 +94,7 @@ func (a *AdtelligentAdapter) MakeBids(bidReq *openrtb2.BidRequest, unused *adapt } var bidResp openrtb2.BidResponse - if err := json.Unmarshal(httpRes.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(httpRes.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("error while decoding response, err: %s", err), }} @@ -154,14 +156,14 @@ func validateImpression(imp *openrtb2.Imp) (int, error) { var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { return 0, &errortypes.BadInput{ Message: fmt.Sprintf("ignoring imp id=%s, error while decoding extImpBidder, err: %s", imp.ID, err), } } impExt := openrtb_ext.ExtImpAdtelligent{} - err := json.Unmarshal(bidderExt.Bidder, &impExt) + err := jsonutil.Unmarshal(bidderExt.Bidder, &impExt) if err != nil { return 0, &errortypes.BadInput{ Message: fmt.Sprintf("ignoring imp id=%s, error while decoding impExt, err: %s", imp.ID, err), @@ -174,6 +176,11 @@ func validateImpression(imp *openrtb2.Imp) (int, error) { impExtBuffer, err = json.Marshal(&adtelligentImpExt{ Adtelligent: impExt, }) + if err != nil { + return 0, &errortypes.BadInput{ + Message: fmt.Sprintf("ignoring imp id=%s, error while marshaling impExt, err: %s", imp.ID, err), + } + } if impExt.BidFloor > 0 { imp.BidFloor = impExt.BidFloor @@ -181,7 +188,13 @@ func validateImpression(imp *openrtb2.Imp) (int, error) { imp.Ext = impExtBuffer - return impExt.SourceId, nil + aid, err := impExt.SourceId.Int64() + if err != nil { + return 0, &errortypes.BadInput{ + Message: fmt.Sprintf("ignoring imp id=%s, aid parsing err: %s", imp.ID, err), + } + } + return int(aid), nil } // Builder builds a new instance of the Adtelligent adapter for the given bidder with the given config. diff --git a/adapters/adtelligent/adtelligent_test.go b/adapters/adtelligent/adtelligent_test.go index 948710387b3..64a4573ac19 100644 --- a/adapters/adtelligent/adtelligent_test.go +++ b/adapters/adtelligent/adtelligent_test.go @@ -3,9 +3,9 @@ package adtelligent import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adtelligent/adtelligenttest/exemplary/media-type-mapping.json b/adapters/adtelligent/adtelligenttest/exemplary/media-type-mapping.json index 553ec61833b..cd64601911d 100644 --- a/adapters/adtelligent/adtelligenttest/exemplary/media-type-mapping.json +++ b/adapters/adtelligent/adtelligenttest/exemplary/media-type-mapping.json @@ -45,7 +45,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adtelligent/adtelligenttest/exemplary/simple-banner.json b/adapters/adtelligent/adtelligenttest/exemplary/simple-banner.json index 8c6fe3ade1b..dac9180251b 100644 --- a/adapters/adtelligent/adtelligenttest/exemplary/simple-banner.json +++ b/adapters/adtelligent/adtelligenttest/exemplary/simple-banner.json @@ -52,7 +52,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/adtelligent/adtelligenttest/exemplary/simple-video.json b/adapters/adtelligent/adtelligenttest/exemplary/simple-video.json index aabcf952f78..8288137b7fc 100644 --- a/adapters/adtelligent/adtelligenttest/exemplary/simple-video.json +++ b/adapters/adtelligent/adtelligenttest/exemplary/simple-video.json @@ -45,7 +45,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/adtelligent/adtelligenttest/supplemental/explicit-dimensions.json b/adapters/adtelligent/adtelligenttest/supplemental/explicit-dimensions.json index 7d6ce5b8084..d73d95d3d75 100644 --- a/adapters/adtelligent/adtelligenttest/supplemental/explicit-dimensions.json +++ b/adapters/adtelligent/adtelligenttest/supplemental/explicit-dimensions.json @@ -48,7 +48,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-ext.json b/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-ext.json index 63c587ed742..3d7508d2103 100644 --- a/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-ext.json +++ b/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-ext.json @@ -19,7 +19,7 @@ "expectedMakeRequestsErrors": [ { - "value": "ignoring imp id=unsupported-native-imp, error while decoding impExt, err: json: cannot unmarshal string into Go struct field ExtImpAdtelligent.aid of type int", + "value": "ignoring imp id=unsupported-native-imp, error while marshaling impExt, err: json: invalid number literal \"some string instead of int\"", "comparison": "literal" } ] diff --git a/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-mapping.json b/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-mapping.json index 894e9fbe04d..5d43a8683df 100644 --- a/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-mapping.json +++ b/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-mapping.json @@ -45,7 +45,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adtelligent/params_test.go b/adapters/adtelligent/params_test.go index 227920b25b4..bd3a2b85833 100644 --- a/adapters/adtelligent/params_test.go +++ b/adapters/adtelligent/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // This file actually intends to test static/bidder-params/adtelligent.json @@ -33,16 +33,21 @@ func TestInvalidParams(t *testing.T) { for _, invalidParam := range invalidParams { if err := validator.Validate(openrtb_ext.BidderAdtelligent, json.RawMessage(invalidParam)); err == nil { - t.Errorf("Schema allowed unexpected params: %s", invalidParam) + ext := openrtb_ext.ExtImpAdtelligent{} + err = json.Unmarshal([]byte(invalidParam), &ext) + if err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } } } } var validParams = []string{ `{"aid":123}`, + `{"aid":"123"}`, `{"aid":123,"placementId":1234}`, `{"aid":123,"siteId":4321}`, - `{"aid":123,"siteId":0,"bidFloor":0}`, + `{"aid":"123","siteId":0,"bidFloor":0}`, } var invalidParams = []string{ @@ -53,8 +58,7 @@ var invalidParams = []string{ `4.2`, `[]`, `{}`, - `{"aid":"123"}`, - `{"aid":"0"}`, + `{"aid":"qwerty"}`, `{"aid":"123","placementId":"123"}`, `{"aid":123, "placementId":"123", "siteId":"321"}`, } diff --git a/adapters/adtonos/adtonos.go b/adapters/adtonos/adtonos.go new file mode 100644 index 00000000000..e3c20cd9322 --- /dev/null +++ b/adapters/adtonos/adtonos.go @@ -0,0 +1,143 @@ +package adtonos + +import ( + "encoding/json" + "fmt" + "net/http" + "text/template" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +type adapter struct { + endpointTemplate *template.Template +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + template, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + + bidder := &adapter{ + endpointTemplate: template, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var bidderExt adapters.ExtImpBidder + if err := jsonutil.Unmarshal(request.Imp[0].Ext, &bidderExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Invalid imp.ext for impression index %d. Error Infomation: %s", 0, err.Error()), + }} + } + var impExt openrtb_ext.ImpExtAdTonos + if err := jsonutil.Unmarshal(bidderExt.Bidder, &impExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Invalid imp.ext.bidder for impression index %d. Error Infomation: %s", 0, err.Error()), + }} + } + + endpoint, err := a.buildEndpointURL(&impExt) + if err != nil { + return nil, []error{err} + } + + requestJson, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: endpoint, + Body: requestJson, + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), + } + + return []*adapters.RequestData{requestData}, nil +} + +func (a *adapter) buildEndpointURL(params *openrtb_ext.ImpExtAdTonos) (string, error) { + endpointParams := macros.EndpointTemplateParams{PublisherID: params.SupplierID} + return macros.ResolveMacros(a.endpointTemplate, endpointParams) +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + var errors []error + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + bidType, err := getMediaTypeForBid(seatBid.Bid[i], request.Imp) + if err != nil { + errors = append(errors, err) + continue + } + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, errors +} + +func getMediaTypeForBid(bid openrtb2.Bid, requestImps []openrtb2.Imp) (openrtb_ext.BidType, error) { + if bid.MType != 0 { + // If present, use explicit markup type annotation from the bidder: + switch bid.MType { + case openrtb2.MarkupAudio: + return openrtb_ext.BidTypeAudio, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + } + } + // As a fallback, guess markup type based on requested type - AdTonos is an audio company so we prioritize that. + for _, requestImp := range requestImps { + if requestImp.ID == bid.ImpID { + if requestImp.Audio != nil { + return openrtb_ext.BidTypeAudio, nil + } else if requestImp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } else { + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Unsupported bidtype for bid: \"%s\"", bid.ImpID), + } + } + } + } + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find impression: \"%s\"", bid.ImpID), + } +} diff --git a/adapters/adtonos/adtonos_test.go b/adapters/adtonos/adtonos_test.go new file mode 100644 index 00000000000..683ce3e4458 --- /dev/null +++ b/adapters/adtonos/adtonos_test.go @@ -0,0 +1,30 @@ +package adtonos + +import ( + "testing" + + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderAdTonos, config.Adapter{ + Endpoint: "http://exchange.example.com/bid/{{.PublisherID}}"}, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "adtonostest", bidder) +} + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderAdTonos, config.Adapter{ + Endpoint: "{{Malformed}}"}, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + assert.Error(t, buildErr) +} diff --git a/adapters/adtonos/adtonostest/exemplary/simple-audio-with-mtype.json b/adapters/adtonos/adtonostest/exemplary/simple-audio-with-mtype.json new file mode 100644 index 00000000000..c9103e37ee4 --- /dev/null +++ b/adapters/adtonos/adtonostest/exemplary/simple-audio-with-mtype.json @@ -0,0 +1,115 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "imp": [ + { + "id": "some-impression-id", + "bidfloor": 4.2, + "ext": { + "bidder": { + "supplierId": "777XYZ123" + } + }, + "audio": { + "mimes": [ + "audio/mpeg" + ] + } + } + ], + "test": 1, + "site": { + "publisher": { + "id": "1" + }, + "page": "http://www.example.com", + "domain": "www.example.com" + }, + "device": {} + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://exchange.example.com/bid/777XYZ123", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "some-request-id", + "tmax": 1000, + "imp": [ + { + "id": "some-impression-id", + "bidfloor": 4.2, + "ext": { + "bidder": { + "supplierId": "777XYZ123" + } + }, + "audio": { + "mimes": [ + "audio/mpeg" + ] + } + } + ], + "site": { + "publisher": { + "id": "1" + }, + "page": "http://www.example.com", + "domain": "www.example.com" + }, + "device": {}, + "test": 1 + }, + "impIDs":["some-impression-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some-request-id", + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "1", + "impid": "some-impression-id", + "crid": "some-creative-id", + "adm": "TAG", + "price": 6.5, + "mtype": 3 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "some-impression-id", + "crid": "some-creative-id", + "adm": "TAG", + "price": 6.5, + "mtype": 3 + }, + "type": "audio" + } + ] + } + ] +} diff --git a/adapters/adtonos/adtonostest/exemplary/simple-audio.json b/adapters/adtonos/adtonostest/exemplary/simple-audio.json new file mode 100644 index 00000000000..61cac002660 --- /dev/null +++ b/adapters/adtonos/adtonostest/exemplary/simple-audio.json @@ -0,0 +1,113 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "imp": [ + { + "id": "some-impression-id", + "bidfloor": 4.2, + "ext": { + "bidder": { + "supplierId": "777XYZ123" + } + }, + "audio": { + "mimes": [ + "audio/mpeg" + ] + } + } + ], + "test": 1, + "site": { + "publisher": { + "id": "1" + }, + "page": "http://www.example.com", + "domain": "www.example.com" + }, + "device": {} + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://exchange.example.com/bid/777XYZ123", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "some-request-id", + "tmax": 1000, + "imp": [ + { + "id": "some-impression-id", + "bidfloor": 4.2, + "ext": { + "bidder": { + "supplierId": "777XYZ123" + } + }, + "audio": { + "mimes": [ + "audio/mpeg" + ] + } + } + ], + "site": { + "publisher": { + "id": "1" + }, + "page": "http://www.example.com", + "domain": "www.example.com" + }, + "device": {}, + "test": 1 + }, + "impIDs":["some-impression-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some-request-id", + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "1", + "impid": "some-impression-id", + "crid": "some-creative-id", + "adm": "TAG", + "price": 6.5 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "some-impression-id", + "crid": "some-creative-id", + "adm": "TAG", + "price": 6.5 + }, + "type": "audio" + } + ] + } + ] +} diff --git a/adapters/adtonos/adtonostest/exemplary/simple-video.json b/adapters/adtonos/adtonostest/exemplary/simple-video.json new file mode 100644 index 00000000000..f00089b4008 --- /dev/null +++ b/adapters/adtonos/adtonostest/exemplary/simple-video.json @@ -0,0 +1,113 @@ +{ + "mockBidRequest": { + "id": "video-request-id", + "tmax": 1000, + "imp": [ + { + "id": "some-impression-id", + "bidfloor": 4.2, + "ext": { + "bidder": { + "supplierId": "777XYZ123" + } + }, + "video": { + "mimes": [ + "video/mp4" + ] + } + } + ], + "test": 1, + "site": { + "publisher": { + "id": "1" + }, + "page": "http://www.example.com", + "domain": "www.example.com" + }, + "device": {} + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://exchange.example.com/bid/777XYZ123", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "video-request-id", + "tmax": 1000, + "imp": [ + { + "id": "some-impression-id", + "bidfloor": 4.2, + "ext": { + "bidder": { + "supplierId": "777XYZ123" + } + }, + "video": { + "mimes": [ + "video/mp4" + ] + } + } + ], + "site": { + "publisher": { + "id": "1" + }, + "page": "http://www.example.com", + "domain": "www.example.com" + }, + "device": {}, + "test": 1 + }, + "impIDs":["some-impression-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "video-request-id", + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "1", + "impid": "some-impression-id", + "crid": "some-creative-id", + "adm": "TAG", + "price": 6.5 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "some-impression-id", + "crid": "some-creative-id", + "adm": "TAG", + "price": 6.5 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/adtonos/adtonostest/supplemental/wrong-impression-mapping.json b/adapters/adtonos/adtonostest/supplemental/wrong-impression-mapping.json new file mode 100644 index 00000000000..6fa20e3d3c3 --- /dev/null +++ b/adapters/adtonos/adtonostest/supplemental/wrong-impression-mapping.json @@ -0,0 +1,103 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "imp": [ + { + "id": "correct-impression-id", + "bidfloor": 4.2, + "ext": { + "bidder": { + "supplierId": "777XYZ123" + } + }, + "audio": { + "mimes": [ + "audio/mpeg" + ] + } + } + ], + "test": 1, + "site": { + "publisher": { + "id": "1" + }, + "page": "http://www.example.com", + "domain": "www.example.com" + }, + "device": {} + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://exchange.example.com/bid/777XYZ123", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "some-request-id", + "tmax": 1000, + "imp": [ + { + "id": "correct-impression-id", + "bidfloor": 4.2, + "ext": { + "bidder": { + "supplierId": "777XYZ123" + } + }, + "audio": { + "mimes": [ + "audio/mpeg" + ] + } + } + ], + "site": { + "publisher": { + "id": "1" + }, + "page": "http://www.example.com", + "domain": "www.example.com" + }, + "device": {}, + "test": 1 + }, + "impIDs":["correct-impression-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some-request-id", + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "1", + "impid": "unexpected-impression-id", + "crid": "some-creative-id", + "adm": "TAG", + "price": 6.5 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [{"currency":"USD","bids":[]}], + "expectedMakeBidsErrors": [ + { + "value": "Failed to find impression: \"unexpected-impression-id\"", + "comparison": "literal" + } + ] +} diff --git a/adapters/adtonos/params_test.go b/adapters/adtonos/params_test.go new file mode 100644 index 00000000000..703446c9def --- /dev/null +++ b/adapters/adtonos/params_test.go @@ -0,0 +1,43 @@ +package adtonos + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderAdTonos, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderAdTonos, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"supplierId": ""}`, + `{"supplierId": "7YZxxxdJMSXWv7SwY"}`, +} + +var invalidParams = []string{ + `{"supplierId": 42}`, +} diff --git a/adapters/adtrgtme/adtrgtme.go b/adapters/adtrgtme/adtrgtme.go index 254bf5051e9..a592cfc7c43 100644 --- a/adapters/adtrgtme/adtrgtme.go +++ b/adapters/adtrgtme/adtrgtme.go @@ -5,12 +5,13 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { @@ -57,6 +58,7 @@ func (v *adapter) MakeRequests( Uri: v.buildRequestURI(siteID), Body: requestBody, Headers: makeRequestHeaders(openRTBRequest), + ImpIDs: openrtb_ext.GetImpIDs(requestCopy.Imp), } requests = append(requests, requestData) @@ -66,13 +68,13 @@ func (v *adapter) MakeRequests( func getSiteIDFromImp(imp *openrtb2.Imp) (uint64, error) { var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { return 0, &errortypes.BadInput{ Message: "ext.bidder not provided", } } var ext openrtb_ext.ExtImpAdtrgtme - if err := json.Unmarshal(bidderExt.Bidder, &ext); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &ext); err != nil { return 0, &errortypes.BadInput{ Message: "ext.bidder not provided", } @@ -146,7 +148,7 @@ func (v *adapter) MakeBids( } var response openrtb2.BidResponse - if err := json.Unmarshal(bidderRawResponse.Body, &response); err != nil { + if err := jsonutil.Unmarshal(bidderRawResponse.Body, &response); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: "Bad Server Response", }} diff --git a/adapters/adtrgtme/adtrgtme_test.go b/adapters/adtrgtme/adtrgtme_test.go index 91d9b233ffe..d57b5943af1 100644 --- a/adapters/adtrgtme/adtrgtme_test.go +++ b/adapters/adtrgtme/adtrgtme_test.go @@ -3,9 +3,9 @@ package adtrgtme import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/adtrgtme/adtrgtmetest/exemplary/banner-app.json b/adapters/adtrgtme/adtrgtmetest/exemplary/banner-app.json index b0849093459..fac5251fbaf 100644 --- a/adapters/adtrgtme/adtrgtmetest/exemplary/banner-app.json +++ b/adapters/adtrgtme/adtrgtmetest/exemplary/banner-app.json @@ -136,7 +136,8 @@ "user": { "id": "super-user-id" } - } + }, + "impIDs":["super-bid-id"] }, "mockResponse": { "status": 200, @@ -220,7 +221,8 @@ "user": { "id": "super-user-id" } - } + }, + "impIDs":["incredible-bid-id"] }, "mockResponse": { "status": 200, @@ -304,7 +306,8 @@ "user": { "id": "super-user-id" } - } + }, + "impIDs":["amazing-bid-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adtrgtme/adtrgtmetest/exemplary/banner-web.json b/adapters/adtrgtme/adtrgtmetest/exemplary/banner-web.json index 0eedafa1bc6..23eb23ff54a 100644 --- a/adapters/adtrgtme/adtrgtmetest/exemplary/banner-web.json +++ b/adapters/adtrgtme/adtrgtmetest/exemplary/banner-web.json @@ -132,7 +132,8 @@ "user": { "id": "super-user-id" } - } + }, + "impIDs":["super-bid-id"] }, "mockResponse": { "status": 200, @@ -214,7 +215,8 @@ "user": { "id": "super-user-id" } - } + }, + "impIDs":["incredible-bid-id"] }, "mockResponse": { "status": 200, @@ -296,7 +298,8 @@ "user": { "id": "super-user-id" } - } + }, + "impIDs":["amazing-bid-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/banner-app-headers-ipv6.json b/adapters/adtrgtme/adtrgtmetest/supplemental/banner-app-headers-ipv6.json index d90f66a4a82..60c8171fa51 100644 --- a/adapters/adtrgtme/adtrgtmetest/supplemental/banner-app-headers-ipv6.json +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/banner-app-headers-ipv6.json @@ -106,7 +106,8 @@ "user": { "id": "test-user-id" } - } + }, + "impIDs":["test-bid-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/banner-web-headers-ipv6.json b/adapters/adtrgtme/adtrgtmetest/supplemental/banner-web-headers-ipv6.json index e2c53b8abb2..0ba55ddac68 100644 --- a/adapters/adtrgtme/adtrgtmetest/supplemental/banner-web-headers-ipv6.json +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/banner-web-headers-ipv6.json @@ -102,7 +102,8 @@ "user": { "id": "test-user-id" } - } + }, + "impIDs":["test-bid-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/empty-seatbid-array.json b/adapters/adtrgtme/adtrgtmetest/supplemental/empty-seatbid-array.json index 648a462059c..7221767005e 100644 --- a/adapters/adtrgtme/adtrgtmetest/supplemental/empty-seatbid-array.json +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/empty-seatbid-array.json @@ -104,7 +104,8 @@ "buyeruid": "test-buyer-id" }, "tmax": 1000 - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/invalid-response.json b/adapters/adtrgtme/adtrgtmetest/supplemental/invalid-response.json index ba03cb8575c..c8527fe2f97 100644 --- a/adapters/adtrgtme/adtrgtmetest/supplemental/invalid-response.json +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/invalid-response.json @@ -94,7 +94,8 @@ "buyeruid": "test-buyer-id" }, "tmax": 1000 - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/not-found-imp.json b/adapters/adtrgtme/adtrgtmetest/supplemental/not-found-imp.json index 448aa6e49b0..a86e2442b65 100644 --- a/adapters/adtrgtme/adtrgtmetest/supplemental/not-found-imp.json +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/not-found-imp.json @@ -94,7 +94,8 @@ "buyeruid": "test-user-id" }, "tmax": 1000 - } + }, + "impIDs":["test-unsupported-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-bad-request.json b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-bad-request.json index a883ad47294..5b4a2fa9419 100644 --- a/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-bad-request.json +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-bad-request.json @@ -75,7 +75,8 @@ "buyeruid": "test-user" }, "tmax": 1000 - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400 diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-internal-server-error.json b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-internal-server-error.json index af924a95149..c533e08f9b8 100644 --- a/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-internal-server-error.json +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-internal-server-error.json @@ -65,7 +65,8 @@ "buyeruid": "test-user" }, "tmax": 1000 - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 500 diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-no-content.json b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-no-content.json index 0693fbba04f..b60bcac8944 100644 --- a/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-no-content.json +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-no-content.json @@ -65,7 +65,8 @@ "buyeruid": "test-user" }, "tmax": 1000 - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-service-unavaliable.json b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-service-unavaliable.json index bc749628315..73e2c677dc5 100644 --- a/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-service-unavaliable.json +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-service-unavaliable.json @@ -65,7 +65,8 @@ "buyeruid": "test-user" }, "tmax": 1000 - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 503 diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-temporary-redirect.json b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-temporary-redirect.json index a97e7e9663b..3bbd06cd5e2 100644 --- a/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-temporary-redirect.json +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/status-code-temporary-redirect.json @@ -65,7 +65,8 @@ "buyeruid": "test-user" }, "tmax": 1000 - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 307 diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/unsupported-bid-type-native.json b/adapters/adtrgtme/adtrgtmetest/supplemental/unsupported-bid-type-native.json index dc6389eb97b..dd320e4b0db 100644 --- a/adapters/adtrgtme/adtrgtmetest/supplemental/unsupported-bid-type-native.json +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/unsupported-bid-type-native.json @@ -84,7 +84,8 @@ "buyeruid": "test-user-id" }, "tmax": 1000 - } + }, + "impIDs":["test-unsupported-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adtrgtme/adtrgtmetest/supplemental/unsupported-bid-type-video.json b/adapters/adtrgtme/adtrgtmetest/supplemental/unsupported-bid-type-video.json index 9c374420f6f..7826be2de3a 100644 --- a/adapters/adtrgtme/adtrgtmetest/supplemental/unsupported-bid-type-video.json +++ b/adapters/adtrgtme/adtrgtmetest/supplemental/unsupported-bid-type-video.json @@ -94,7 +94,8 @@ "buyeruid": "test-user-id" }, "tmax": 1000 - } + }, + "impIDs":["test-unsupported-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adtrgtme/params_test.go b/adapters/adtrgtme/params_test.go index e89f8423ed4..19acaedc880 100644 --- a/adapters/adtrgtme/params_test.go +++ b/adapters/adtrgtme/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/advangelists/advangelists.go b/adapters/advangelists/advangelists.go index 19bb4c326e2..1d4cd4fb648 100644 --- a/adapters/advangelists/advangelists.go +++ b/adapters/advangelists/advangelists.go @@ -6,12 +6,13 @@ import ( "net/http" "text/template" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type AdvangelistsAdapter struct { @@ -129,13 +130,13 @@ func compatBannerImpression(imp *openrtb2.Imp) error { func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpAdvangelists, error) { var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ Message: err.Error(), } } var advangelistsExt openrtb_ext.ExtImpAdvangelists - if err := json.Unmarshal(bidderExt.Bidder, &advangelistsExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &advangelistsExt); err != nil { return nil, &errortypes.BadInput{ Message: err.Error(), } @@ -164,7 +165,8 @@ func (adapter *AdvangelistsAdapter) buildAdapterRequest(prebidBidRequest *openrt Method: "POST", Uri: url, Body: reqJSON, - Headers: headers}, nil + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(imps)}, nil } func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpAdvangelists, imps []openrtb2.Imp) *openrtb2.BidRequest { @@ -198,7 +200,7 @@ func (adapter *AdvangelistsAdapter) buildEndpointURL(params *openrtb_ext.ExtImpA // MakeBids translates advangelists bid response to prebid-server specific format func (adapter *AdvangelistsAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - var msg = "" + var msg string if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -207,7 +209,7 @@ func (adapter *AdvangelistsAdapter) MakeBids(internalRequest *openrtb2.BidReques return nil, []error{&errortypes.BadServerResponse{Message: msg}} } var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { msg = fmt.Sprintf("Bad server response: %d", err) return nil, []error{&errortypes.BadServerResponse{Message: msg}} } diff --git a/adapters/advangelists/advangelists_test.go b/adapters/advangelists/advangelists_test.go index e4c5debaa79..64ae4c89fa1 100644 --- a/adapters/advangelists/advangelists_test.go +++ b/adapters/advangelists/advangelists_test.go @@ -3,9 +3,9 @@ package advangelists import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/advangelists/advangeliststest/exemplary/banner.json b/adapters/advangelists/advangeliststest/exemplary/banner.json index b011137cb9f..6aa803c7e86 100644 --- a/adapters/advangelists/advangeliststest/exemplary/banner.json +++ b/adapters/advangelists/advangeliststest/exemplary/banner.json @@ -50,7 +50,8 @@ } }] - } + }, + "impIDs":["testimpid"] }, "mockResponse": { "status": 200, diff --git a/adapters/advangelists/advangeliststest/exemplary/video.json b/adapters/advangelists/advangeliststest/exemplary/video.json index 28a78753d80..ea9e955d67a 100644 --- a/adapters/advangelists/advangeliststest/exemplary/video.json +++ b/adapters/advangelists/advangeliststest/exemplary/video.json @@ -38,7 +38,8 @@ "h": 480 } }] - } + }, + "impIDs":["testimpid"] }, "mockResponse": { "status": 200, diff --git a/adapters/advangelists/params_test.go b/adapters/advangelists/params_test.go index a58217a0ffd..8286f7f84ac 100644 --- a/adapters/advangelists/params_test.go +++ b/adapters/advangelists/params_test.go @@ -2,8 +2,9 @@ package advangelists import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/adview/adview.go b/adapters/adview/adview.go index d2181b6591a..55bf2284504 100644 --- a/adapters/adview/adview.go +++ b/adapters/adview/adview.go @@ -7,18 +7,23 @@ import ( "strings" "text/template" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { endpoint *template.Template } +type adviewBidExt struct { + BidType int `json:"formattype,omitempty"` +} + // Builder builds a new instance of the adview adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { endpointTemplate, err := template.New("endpointTemplate").Parse(config.Endpoint) @@ -33,63 +38,79 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co } func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - var bidderExt adapters.ExtImpBidder - imp := &request.Imp[0] - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("invalid imp.ext, %s", err.Error()), - }} - } - //use adview - var advImpExt openrtb_ext.ExtImpAdView - if err := json.Unmarshal(bidderExt.Bidder, &advImpExt); err != nil { - return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("invalid bidderExt.Bidder, %s", err.Error()), - }} - } - imp.TagID = advImpExt.MasterTagID //tagid means posid - //for adview bid request - if imp.Banner != nil { - if len(imp.Banner.Format) != 0 { - bannerCopy := *imp.Banner - bannerCopy.H = &imp.Banner.Format[0].H - bannerCopy.W = &imp.Banner.Format[0].W - imp.Banner = &bannerCopy + var requests []*adapters.RequestData + var errors []error + + //must copy the original request. + requestCopy := *request + for _, imp := range request.Imp { + var bidderExt adapters.ExtImpBidder + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { + errors = append(errors, &errortypes.BadInput{ + Message: fmt.Sprintf("invalid imp.ext, %s", err.Error()), + }) + continue + } + //use adview + var advImpExt openrtb_ext.ExtImpAdView + if err := jsonutil.Unmarshal(bidderExt.Bidder, &advImpExt); err != nil { + errors = append(errors, &errortypes.BadInput{ + Message: fmt.Sprintf("invalid bidderExt.Bidder, %s", err.Error()), + }) + continue } - } - // Check if imp comes with bid floor amount defined in a foreign currency - if imp.BidFloor > 0 && imp.BidFloorCur != "" && strings.ToUpper(imp.BidFloorCur) != "USD" { - // Convert to US dollars - convertedValue, err := requestInfo.ConvertCurrency(imp.BidFloor, imp.BidFloorCur, "USD") - if err != nil { - return nil, []error{err} + imp.TagID = advImpExt.MasterTagID //tagid means posid + //for adview bid request + if imp.Banner != nil { + if len(imp.Banner.Format) != 0 { + bannerCopy := *imp.Banner + bannerCopy.H = &imp.Banner.Format[0].H + bannerCopy.W = &imp.Banner.Format[0].W + imp.Banner = &bannerCopy + } } - // Update after conversion. All imp elements inside request.Imp are shallow copies - // therefore, their non-pointer values are not shared memory and are safe to modify. - imp.BidFloorCur = "USD" - imp.BidFloor = convertedValue - } - // Set the CUR of bid to USD after converting all floors - request.Cur = []string{"USD"} + // Check if imp comes with bid floor amount defined in a foreign currency + if imp.BidFloor > 0 && imp.BidFloorCur != "" && strings.ToUpper(imp.BidFloorCur) != "USD" { + // Convert to US dollars + convertedValue, err := requestInfo.ConvertCurrency(imp.BidFloor, imp.BidFloorCur, "USD") + if err != nil { + errors = append(errors, err) + continue + } + // Update after conversion. All imp elements inside request.Imp are shallow copies + // therefore, their non-pointer values are not shared memory and are safe to modify. + imp.BidFloorCur = "USD" + imp.BidFloor = convertedValue + } - url, err := a.buildEndpointURL(&advImpExt) - if err != nil { - return nil, []error{err} - } + // Set the CUR of bid to USD after converting all floors + requestCopy.Cur = []string{"USD"} + requestCopy.Imp = []openrtb2.Imp{imp} - reqJSON, err := json.Marshal(request) - if err != nil { - return nil, []error{err} - } + url, err := a.buildEndpointURL(&advImpExt) + if err != nil { + errors = append(errors, err) + continue + } + + reqJSON, err := json.Marshal(requestCopy) //request + if err != nil { + errors = append(errors, err) + continue + } - return []*adapters.RequestData{{ - Method: http.MethodPost, - Body: reqJSON, - Uri: url, - }}, nil + requestData := &adapters.RequestData{ + Method: http.MethodPost, + Uri: url, + Body: reqJSON, + ImpIDs: openrtb_ext.GetImpIDs(requestCopy.Imp), + } + requests = append(requests, requestData) + } + return requests, errors } func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { @@ -112,17 +133,18 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R } var response openrtb2.BidResponse - if err := json.Unmarshal(responseData.Body, &response); err != nil { + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { return nil, []error{err} } bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) - bidResponse.Currency = "USD" //we just support USD for resp + //we just support USD for resp + bidResponse.Currency = "USD" var errors []error for _, seatBid := range response.SeatBid { for i, bid := range seatBid.Bid { - bidType, err := getMediaTypeForImp(bid.ImpID, request.Imp) + bidType, err := getMediaTypeForBid(bid) if err != nil { errors = append(errors, err) continue @@ -143,17 +165,15 @@ func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtImpAdView) (string, er return macros.ResolveMacros(a.endpoint, endpointParams) } -func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { - mediaType := openrtb_ext.BidTypeBanner - for _, imp := range imps { - if imp.ID == impID { - if imp.Video != nil { - mediaType = openrtb_ext.BidTypeVideo - } else if imp.Native != nil { - mediaType = openrtb_ext.BidTypeNative - } - return mediaType, nil - } +func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return "", fmt.Errorf("Unable to fetch mediaType in impID: %s, mType: %d", bid.ImpID, bid.MType) } - return mediaType, nil } diff --git a/adapters/adview/adview_test.go b/adapters/adview/adview_test.go index d0c993cfb56..08e824a097b 100644 --- a/adapters/adview/adview_test.go +++ b/adapters/adview/adview_test.go @@ -3,9 +3,9 @@ package adview import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/adview/adviewtest/exemplary/banner-app-format.json b/adapters/adview/adviewtest/exemplary/banner-app-format.json index 0ab4951511e..44787106744 100644 --- a/adapters/adview/adviewtest/exemplary/banner-app-format.json +++ b/adapters/adview/adviewtest/exemplary/banner-app-format.json @@ -96,7 +96,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, @@ -115,7 +116,11 @@ ], "crid": "20", "w": 320, - "h": 50 + "h": 50, + "mtype": 1, + "ext": { + "formattype": 0 + } } ], "type": "banner", @@ -148,7 +153,11 @@ ], "crid": "20", "w": 320, - "h": 50 + "h": 50, + "mtype": 1, + "ext": { + "formattype": 0 + } }, "type": "banner" } diff --git a/adapters/adview/adviewtest/exemplary/banner-app-resp-no-formattype.json b/adapters/adview/adviewtest/exemplary/banner-app-resp-no-formattype.json new file mode 100644 index 00000000000..156f941fad2 --- /dev/null +++ b/adapters/adview/adviewtest/exemplary/banner-app-resp-no-formattype.json @@ -0,0 +1,151 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "ifa":"00000000-0000-0000-0000-000000000000", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "placementId": "posid00001", + "accountId": "accountid01" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.adview.com/agent/thirdAdxService/accountid01", + "body": { + "id": "some-request-id", + "cur": ["USD"], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "ifa":"00000000-0000-0000-0000-000000000000", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "tagid": "posid00001", + "ext": { + "bidder": { + "placementId": "posid00001", + "accountId": "accountid01" + } + } + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + }, + "impIDs": ["some-impression-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "mtype": 1 + } + ], + "type": "banner", + "seat": "adview" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adview": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adview/adviewtest/exemplary/banner-app.json b/adapters/adview/adviewtest/exemplary/banner-app.json index 6aad1e8dc05..65b09b61616 100644 --- a/adapters/adview/adviewtest/exemplary/banner-app.json +++ b/adapters/adview/adviewtest/exemplary/banner-app.json @@ -86,7 +86,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, @@ -105,7 +106,11 @@ ], "crid": "20", "w": 320, - "h": 50 + "h": 50, + "mtype": 1, + "ext": { + "formattype": 0 + } } ], "type": "banner", @@ -138,7 +143,11 @@ ], "crid": "20", "w": 320, - "h": 50 + "h": 50, + "mtype": 1, + "ext": { + "formattype": 0 + } }, "type": "banner" } diff --git a/adapters/adview/adviewtest/exemplary/native-app.json b/adapters/adview/adviewtest/exemplary/native-app.json index 804494e5ff5..70b6f8daacc 100644 --- a/adapters/adview/adviewtest/exemplary/native-app.json +++ b/adapters/adview/adviewtest/exemplary/native-app.json @@ -86,7 +86,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, @@ -103,7 +104,11 @@ "adomain": [ "awesome.com" ], - "crid": "20" + "crid": "20", + "mtype": 4, + "ext": { + "formattype": 2 + } } ], "type": "native", @@ -132,9 +137,13 @@ "price": 3.5, "adm": "awesome-markup", "crid": "20", + "mtype": 4, "adomain": [ "awesome.com" - ] + ], + "ext": { + "formattype": 2 + } }, "type": "native" } diff --git a/adapters/adview/adviewtest/exemplary/video-app.json b/adapters/adview/adviewtest/exemplary/video-app.json index 57c9b85598b..491fbbcc456 100644 --- a/adapters/adview/adviewtest/exemplary/video-app.json +++ b/adapters/adview/adviewtest/exemplary/video-app.json @@ -96,7 +96,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, @@ -115,7 +116,11 @@ ], "crid": "20", "w": 1280, - "h": 720 + "h": 720, + "mtype": 2, + "ext": { + "formattype": 1 + } } ], "seat": "adview" @@ -147,7 +152,11 @@ "awesome.com" ], "w": 1280, - "h": 720 + "h": 720, + "mtype": 2, + "ext": { + "formattype": 1 + } }, "type": "video" } diff --git a/adapters/adview/adviewtest/supplemental/bad-request.json b/adapters/adview/adviewtest/supplemental/bad-request.json index 0f10fe79062..ae50df385ff 100644 --- a/adapters/adview/adviewtest/supplemental/bad-request.json +++ b/adapters/adview/adviewtest/supplemental/bad-request.json @@ -34,7 +34,8 @@ }, "tagid": "posid00001" }] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400 diff --git a/adapters/adview/adviewtest/supplemental/empty-response.json b/adapters/adview/adviewtest/supplemental/empty-response.json index c080b18d4bb..28a6a208d4a 100644 --- a/adapters/adview/adviewtest/supplemental/empty-response.json +++ b/adapters/adview/adviewtest/supplemental/empty-response.json @@ -34,7 +34,8 @@ }, "tagid": "posid00001" }] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/adview/adviewtest/supplemental/nobid-response.json b/adapters/adview/adviewtest/supplemental/nobid-response.json index 76a96484e12..b32d81d7486 100644 --- a/adapters/adview/adviewtest/supplemental/nobid-response.json +++ b/adapters/adview/adviewtest/supplemental/nobid-response.json @@ -34,7 +34,8 @@ }, "tagid": "posid00001" }] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adview/adviewtest/supplemental/server-error.json b/adapters/adview/adviewtest/supplemental/server-error.json index de3bc9d3721..d45872a2b50 100644 --- a/adapters/adview/adviewtest/supplemental/server-error.json +++ b/adapters/adview/adviewtest/supplemental/server-error.json @@ -34,7 +34,8 @@ }, "tagid": "posid00001" }] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 500, diff --git a/adapters/adview/adviewtest/supplemental/unparsable-response.json b/adapters/adview/adviewtest/supplemental/unparsable-response.json index 18bc2e0f4ed..76f95d1cd6d 100644 --- a/adapters/adview/adviewtest/supplemental/unparsable-response.json +++ b/adapters/adview/adviewtest/supplemental/unparsable-response.json @@ -34,7 +34,8 @@ }, "tagid": "posid00001" }] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -45,7 +46,7 @@ "expectedBidResponses": [], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/adview/params_test.go b/adapters/adview/params_test.go index 6d124e9b556..9dd1c57c2ad 100644 --- a/adapters/adview/params_test.go +++ b/adapters/adview/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/adxcg/adxcg.go b/adapters/adxcg/adxcg.go index 6b489d322b0..3b5e290ca30 100644 --- a/adapters/adxcg/adxcg.go +++ b/adapters/adxcg/adxcg.go @@ -5,11 +5,12 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) // Builder builds a new instance of the Adxcg adapter for the given bidder with the given config. @@ -45,6 +46,7 @@ func (adapter *adapter) MakeRequests( Uri: adapter.endpoint, Body: openRTBRequestJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(openRTBRequest.Imp), } requestsToBidder = append(requestsToBidder, requestToBidder) @@ -80,7 +82,7 @@ func (adapter *adapter) MakeBids( } var openRTBBidderResponse openrtb2.BidResponse - if err := json.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil { + if err := jsonutil.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil { return nil, []error{err} } diff --git a/adapters/adxcg/adxcg_test.go b/adapters/adxcg/adxcg_test.go index aa5f955c372..0d947213923 100644 --- a/adapters/adxcg/adxcg_test.go +++ b/adapters/adxcg/adxcg_test.go @@ -3,9 +3,9 @@ package adxcg import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) const testsDir = "adxcgtest" diff --git a/adapters/adxcg/adxcgtest/exemplary/simple-banner-currency.json b/adapters/adxcg/adxcgtest/exemplary/simple-banner-currency.json index 8678a62c8a7..70f7c66f11e 100644 --- a/adapters/adxcg/adxcgtest/exemplary/simple-banner-currency.json +++ b/adapters/adxcg/adxcgtest/exemplary/simple-banner-currency.json @@ -40,7 +40,8 @@ "bidder": {} } }] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adxcg/adxcgtest/exemplary/simple-banner.json b/adapters/adxcg/adxcgtest/exemplary/simple-banner.json index 42902014126..2171ab538f6 100644 --- a/adapters/adxcg/adxcgtest/exemplary/simple-banner.json +++ b/adapters/adxcg/adxcgtest/exemplary/simple-banner.json @@ -38,7 +38,8 @@ "bidder": {} } }] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adxcg/adxcgtest/exemplary/simple-native.json b/adapters/adxcg/adxcgtest/exemplary/simple-native.json index 1b0d5b5996c..9e96042db60 100644 --- a/adapters/adxcg/adxcgtest/exemplary/simple-native.json +++ b/adapters/adxcg/adxcgtest/exemplary/simple-native.json @@ -26,7 +26,8 @@ } } ] - } + }, + "impIDs":["test-imp-native-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adxcg/adxcgtest/exemplary/simple-video.json b/adapters/adxcg/adxcgtest/exemplary/simple-video.json index 735f10036b8..20589e10620 100644 --- a/adapters/adxcg/adxcgtest/exemplary/simple-video.json +++ b/adapters/adxcg/adxcgtest/exemplary/simple-video.json @@ -58,7 +58,8 @@ } } ] - } + }, + "impIDs":["test-imp-video-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adxcg/adxcgtest/supplemental/bad_response.json b/adapters/adxcg/adxcgtest/supplemental/bad_response.json index f84f5555259..e887a114408 100644 --- a/adapters/adxcg/adxcgtest/supplemental/bad_response.json +++ b/adapters/adxcg/adxcgtest/supplemental/bad_response.json @@ -43,7 +43,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -54,7 +55,7 @@ "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/adxcg/adxcgtest/supplemental/status_204.json b/adapters/adxcg/adxcgtest/supplemental/status_204.json index 0702c103332..950e5c0d5fe 100644 --- a/adapters/adxcg/adxcgtest/supplemental/status_204.json +++ b/adapters/adxcg/adxcgtest/supplemental/status_204.json @@ -43,7 +43,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/adxcg/adxcgtest/supplemental/status_400.json b/adapters/adxcg/adxcgtest/supplemental/status_400.json index 65d21406bf0..6551dee32ba 100644 --- a/adapters/adxcg/adxcgtest/supplemental/status_400.json +++ b/adapters/adxcg/adxcgtest/supplemental/status_400.json @@ -43,7 +43,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/adxcg/adxcgtest/supplemental/status_418.json b/adapters/adxcg/adxcgtest/supplemental/status_418.json index 4c5dd576aa6..903f7d8a5c2 100644 --- a/adapters/adxcg/adxcgtest/supplemental/status_418.json +++ b/adapters/adxcg/adxcgtest/supplemental/status_418.json @@ -43,7 +43,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 418, diff --git a/adapters/adyoulike/adyoulike.go b/adapters/adyoulike/adyoulike.go index e00e95dccb5..dbc76f3f75b 100644 --- a/adapters/adyoulike/adyoulike.go +++ b/adapters/adyoulike/adyoulike.go @@ -7,11 +7,12 @@ import ( "strings" "github.com/buger/jsonparser" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { @@ -84,6 +85,7 @@ func (a *adapter) MakeRequests( Uri: a.endpoint, Body: openRTBRequestJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(reqCopy.Imp), } requestsToBidder = append(requestsToBidder, requestToBidder) @@ -119,7 +121,7 @@ func (a *adapter) MakeBids( } var openRTBBidderResponse openrtb2.BidResponse - if err := json.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil { + if err := jsonutil.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil { return nil, []error{err} } diff --git a/adapters/adyoulike/adyoulike_test.go b/adapters/adyoulike/adyoulike_test.go index d3000f673fc..5a6c91b1c87 100644 --- a/adapters/adyoulike/adyoulike_test.go +++ b/adapters/adyoulike/adyoulike_test.go @@ -3,9 +3,9 @@ package adyoulike import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) const testsBidderEndpoint = "https://localhost/bid/4" diff --git a/adapters/adyoulike/adyouliketest/exemplary/currency-conversion.json b/adapters/adyoulike/adyouliketest/exemplary/currency-conversion.json index 85e14fbe3e8..2eabe27d679 100644 --- a/adapters/adyoulike/adyouliketest/exemplary/currency-conversion.json +++ b/adapters/adyoulike/adyouliketest/exemplary/currency-conversion.json @@ -74,7 +74,8 @@ } } } - } + }, + "impIDs":["banner-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adyoulike/adyouliketest/exemplary/multiformat-impression.json b/adapters/adyoulike/adyouliketest/exemplary/multiformat-impression.json index 8e69fcf5bc9..a850d3b11c3 100644 --- a/adapters/adyoulike/adyouliketest/exemplary/multiformat-impression.json +++ b/adapters/adyoulike/adyouliketest/exemplary/multiformat-impression.json @@ -95,7 +95,8 @@ "tagid": "123123" } ] - } + }, + "impIDs":["banner-imp-id","video-imp-id","native-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/adyoulike/adyouliketest/supplemental/invalid-bid-response.json b/adapters/adyoulike/adyouliketest/supplemental/invalid-bid-response.json index 01a80efdec3..1b0959a471d 100644 --- a/adapters/adyoulike/adyouliketest/supplemental/invalid-bid-response.json +++ b/adapters/adyoulike/adyouliketest/supplemental/invalid-bid-response.json @@ -46,7 +46,8 @@ "tagid": "12345" } ] - } + }, + "impIDs":["banner-imp-id"] }, "mockResponse": { "status": 200, @@ -59,7 +60,7 @@ "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/adyoulike/adyouliketest/supplemental/status-bad-request.json b/adapters/adyoulike/adyouliketest/supplemental/status-bad-request.json index 5c4c2df3d0b..8a1611401ec 100644 --- a/adapters/adyoulike/adyouliketest/supplemental/status-bad-request.json +++ b/adapters/adyoulike/adyouliketest/supplemental/status-bad-request.json @@ -46,7 +46,8 @@ "tagid": "12345" } ] - } + }, + "impIDs":["banner-imp-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/adyoulike/adyouliketest/supplemental/status-no-content.json b/adapters/adyoulike/adyouliketest/supplemental/status-no-content.json index c296128e914..f19fd3eeb46 100644 --- a/adapters/adyoulike/adyouliketest/supplemental/status-no-content.json +++ b/adapters/adyoulike/adyouliketest/supplemental/status-no-content.json @@ -46,7 +46,8 @@ "tagid": "12345" } ] - } + }, + "impIDs":["banner-imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/adyoulike/adyouliketest/supplemental/status-service-unavailable.json b/adapters/adyoulike/adyouliketest/supplemental/status-service-unavailable.json index 445d01e2ccd..5faecb76cf2 100644 --- a/adapters/adyoulike/adyouliketest/supplemental/status-service-unavailable.json +++ b/adapters/adyoulike/adyouliketest/supplemental/status-service-unavailable.json @@ -46,7 +46,8 @@ "tagid": "12345" } ] - } + }, + "impIDs":["banner-imp-id"] }, "mockResponse": { "status": 503, diff --git a/adapters/adyoulike/adyouliketest/supplemental/status-unknown.json b/adapters/adyoulike/adyouliketest/supplemental/status-unknown.json index 5177befdfd9..240d5375785 100644 --- a/adapters/adyoulike/adyouliketest/supplemental/status-unknown.json +++ b/adapters/adyoulike/adyouliketest/supplemental/status-unknown.json @@ -46,7 +46,8 @@ "tagid": "12345" } ] - } + }, + "impIDs":["banner-imp-id"] }, "mockResponse": { "status": 999, diff --git a/adapters/adyoulike/params_test.go b/adapters/adyoulike/params_test.go index 8aebaf2844e..7c6630fbef3 100644 --- a/adapters/adyoulike/params_test.go +++ b/adapters/adyoulike/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // This file actually intends to test static/bidder-params/adyoulike.json diff --git a/adapters/aidem/aidem.go b/adapters/aidem/aidem.go index 9748f32c957..d644d83cdf2 100644 --- a/adapters/aidem/aidem.go +++ b/adapters/aidem/aidem.go @@ -4,16 +4,19 @@ import ( "encoding/json" "fmt" "net/http" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "text/template" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { - endpoint string + EndpointTemplate *template.Template } func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { @@ -23,14 +26,25 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E return nil, []error{err} } + impExt, err := getImpressionExt(&request.Imp[0]) + if err != nil { + return nil, []error{err} + } + + url, err := a.buildEndpointURL(impExt) + if err != nil { + return nil, []error{err} + } + headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") return []*adapters.RequestData{{ Method: "POST", - Uri: a.endpoint, + Uri: url, Body: reqJson, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }}, nil } @@ -48,7 +62,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest } var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("JSON parsing error: %v", err), }} @@ -81,9 +95,15 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest // Builder builds a new instance of the AIDEM adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - return &adapter{ - endpoint: config.Endpoint, - }, nil + urlTemplate, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + + bidder := &adapter{ + EndpointTemplate: urlTemplate, + } + return bidder, nil } func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { @@ -100,3 +120,25 @@ func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { return "", fmt.Errorf("Unable to fetch mediaType in multi-format: %s", bid.ImpID) } } + +func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpAidem, error) { + var bidderExt adapters.ExtImpBidder + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: err.Error(), + } + } + var AIDEMExt openrtb_ext.ExtImpAidem + if err := jsonutil.Unmarshal(bidderExt.Bidder, &AIDEMExt); err != nil { + return nil, &errortypes.BadInput{ + Message: err.Error(), + } + } + return &AIDEMExt, nil +} + +// Builds enpoint url based on adapter-specific pub settings from imp.ext +func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtImpAidem) (string, error) { + endpointParams := macros.EndpointTemplateParams{PublisherID: params.PublisherId} + return macros.ResolveMacros(a.EndpointTemplate, endpointParams) +} diff --git a/adapters/aidem/aidem_test.go b/adapters/aidem/aidem_test.go index 03bcc7e0fb5..395bd2ca78b 100644 --- a/adapters/aidem/aidem_test.go +++ b/adapters/aidem/aidem_test.go @@ -5,14 +5,14 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAidem, config.Adapter{ - Endpoint: "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request", + Endpoint: "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request?billing_id={{.PublisherID}}", }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { @@ -26,5 +26,5 @@ func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderAidem, config.Adapter{ Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - assert.Nil(t, buildErr) + assert.Error(t, buildErr) } diff --git a/adapters/aidem/aidemtest/exemplary/multi-format.json b/adapters/aidem/aidemtest/exemplary/multi-format.json index 0c940d4ba59..b16877f2572 100644 --- a/adapters/aidem/aidemtest/exemplary/multi-format.json +++ b/adapters/aidem/aidemtest/exemplary/multi-format.json @@ -35,7 +35,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request", + "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request?billing_id=1234", "body": { "id": "test-request-id", "imp": [ @@ -68,7 +68,8 @@ } } ] - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 200, diff --git a/adapters/aidem/aidemtest/exemplary/multi-imps-multi-bid.json b/adapters/aidem/aidemtest/exemplary/multi-imps-multi-bid.json index 5c2b36948f7..dd92c169cdc 100644 --- a/adapters/aidem/aidemtest/exemplary/multi-imps-multi-bid.json +++ b/adapters/aidem/aidemtest/exemplary/multi-imps-multi-bid.json @@ -41,7 +41,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request", + "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request?billing_id=1234", "body": { "id": "test-request-id", "imp": [ @@ -80,7 +80,8 @@ } } ] - } + }, + "impIDs":["1","2"] }, "mockResponse": { "status": 200, diff --git a/adapters/aidem/aidemtest/exemplary/multi-imps-single-bid.json b/adapters/aidem/aidemtest/exemplary/multi-imps-single-bid.json index 9eae7101a90..e0006eb0ad1 100644 --- a/adapters/aidem/aidemtest/exemplary/multi-imps-single-bid.json +++ b/adapters/aidem/aidemtest/exemplary/multi-imps-single-bid.json @@ -41,7 +41,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request", + "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request?billing_id=1234", "body": { "id": "test-request-id", "imp": [ @@ -80,7 +80,8 @@ } } ] - } + }, + "impIDs":["1","2"] }, "mockResponse": { "status": 200, diff --git a/adapters/aidem/aidemtest/exemplary/no-bid.json b/adapters/aidem/aidemtest/exemplary/no-bid.json index 7418425f10b..c8c7b7142c3 100644 --- a/adapters/aidem/aidemtest/exemplary/no-bid.json +++ b/adapters/aidem/aidemtest/exemplary/no-bid.json @@ -24,7 +24,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request", + "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request?billing_id=1234", "body": { "id": "test-request-id", "imp": [ @@ -46,7 +46,8 @@ } } ] - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 204, diff --git a/adapters/aidem/aidemtest/exemplary/optional-params.json b/adapters/aidem/aidemtest/exemplary/optional-params.json index 69511fb595c..fa36a407ce3 100644 --- a/adapters/aidem/aidemtest/exemplary/optional-params.json +++ b/adapters/aidem/aidemtest/exemplary/optional-params.json @@ -25,7 +25,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request", + "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request?billing_id=1234", "body": { "id": "test-request-id", "imp": [ @@ -48,7 +48,8 @@ } } ] - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 204, diff --git a/adapters/aidem/aidemtest/exemplary/simple-banner.json b/adapters/aidem/aidemtest/exemplary/simple-banner.json index 73db297ee42..d1d75559392 100644 --- a/adapters/aidem/aidemtest/exemplary/simple-banner.json +++ b/adapters/aidem/aidemtest/exemplary/simple-banner.json @@ -24,7 +24,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request", + "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request?billing_id=1234", "body": { "id": "test-request-id", "imp": [ @@ -46,7 +46,8 @@ } } ] - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 200, diff --git a/adapters/aidem/aidemtest/exemplary/simple-video.json b/adapters/aidem/aidemtest/exemplary/simple-video.json index 0daaffaa8cf..c4f8edd82af 100644 --- a/adapters/aidem/aidemtest/exemplary/simple-video.json +++ b/adapters/aidem/aidemtest/exemplary/simple-video.json @@ -27,7 +27,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request", + "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request?billing_id=1234", "body": { "id": "test-request-id", "imp": [ @@ -52,7 +52,8 @@ } } ] - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 200, diff --git a/adapters/aidem/aidemtest/supplemental/invalid-req-400-status-code-bad-request.json b/adapters/aidem/aidemtest/supplemental/invalid-req-400-status-code-bad-request.json index 3dea13ef7c9..913ff001624 100644 --- a/adapters/aidem/aidemtest/supplemental/invalid-req-400-status-code-bad-request.json +++ b/adapters/aidem/aidemtest/supplemental/invalid-req-400-status-code-bad-request.json @@ -41,7 +41,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request", + "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request?billing_id=1234", "body": { "id": "some-request-id", "imp": [ @@ -80,7 +80,8 @@ "buyeruid": "0000-000-000-0000" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 400 diff --git a/adapters/aidem/aidemtest/supplemental/invalid-res-200-status-code-empty-bids.json b/adapters/aidem/aidemtest/supplemental/invalid-res-200-status-code-empty-bids.json index dd64125f467..7396331f2c5 100644 --- a/adapters/aidem/aidemtest/supplemental/invalid-res-200-status-code-empty-bids.json +++ b/adapters/aidem/aidemtest/supplemental/invalid-res-200-status-code-empty-bids.json @@ -41,7 +41,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request", + "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request?billing_id=1234", "body": { "id": "some-request-id", "imp": [ @@ -80,7 +80,8 @@ "buyeruid": "0000-000-000-0000" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/aidem/aidemtest/supplemental/invalid-resp-multi-imp-type.json b/adapters/aidem/aidemtest/supplemental/invalid-resp-multi-imp-type.json index 95f97d31f72..204cafc9e7e 100644 --- a/adapters/aidem/aidemtest/supplemental/invalid-resp-multi-imp-type.json +++ b/adapters/aidem/aidemtest/supplemental/invalid-resp-multi-imp-type.json @@ -57,7 +57,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request", + "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request?billing_id=1234", "body": { "id": "test-request-id", "imp": [ @@ -112,7 +112,8 @@ "gdpr": 0 } } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/aidem/aidemtest/supplemental/valid-req-200-bid-response-from-aidem.json b/adapters/aidem/aidemtest/supplemental/valid-req-200-bid-response-from-aidem.json index cbd534cd91c..94e888b893d 100644 --- a/adapters/aidem/aidemtest/supplemental/valid-req-200-bid-response-from-aidem.json +++ b/adapters/aidem/aidemtest/supplemental/valid-req-200-bid-response-from-aidem.json @@ -44,7 +44,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request", + "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request?billing_id=1234", "body": { "id": "test-request-id", "imp": [ @@ -86,7 +86,8 @@ "gdpr": 0 } } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/aidem/aidemtest/supplemental/valid-req-204-response-from-aidem.json b/adapters/aidem/aidemtest/supplemental/valid-req-204-response-from-aidem.json index ddd4e5a7735..0813f9cee8f 100644 --- a/adapters/aidem/aidemtest/supplemental/valid-req-204-response-from-aidem.json +++ b/adapters/aidem/aidemtest/supplemental/valid-req-204-response-from-aidem.json @@ -28,7 +28,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request", + "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request?billing_id=1234", "body": { "app": { "bundle": "com.example.app" @@ -54,7 +54,8 @@ "id": "imp-id" } ] - } + }, + "impIDs":["imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/aidem/params_test.go b/adapters/aidem/params_test.go index 36190c0bc9f..bea1d26e3d1 100644 --- a/adapters/aidem/params_test.go +++ b/adapters/aidem/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // This file actually intends to test static/bidder-params/aidem.json TODO: MUST BE CREATED diff --git a/adapters/aja/aja.go b/adapters/aja/aja.go index 5a8afb00045..82007b02d0b 100644 --- a/adapters/aja/aja.go +++ b/adapters/aja/aja.go @@ -5,11 +5,12 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type AJAAdapter struct { @@ -48,6 +49,7 @@ func (a *AJAAdapter) MakeRequests(bidReq *openrtb2.BidRequest, extraInfo *adapte Method: "POST", Uri: a.endpoint, Body: body, + ImpIDs: openrtb_ext.GetImpIDs(req.Imp), }) } @@ -60,13 +62,13 @@ func parseExtAJA(imp openrtb2.Imp) (openrtb_ext.ExtImpAJA, error) { extAJA openrtb_ext.ExtImpAJA ) - if err := json.Unmarshal(imp.Ext, &extImp); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &extImp); err != nil { return extAJA, &errortypes.BadInput{ Message: fmt.Sprintf("Failed to unmarshal ext impID: %s err: %s", imp.ID, err), } } - if err := json.Unmarshal(extImp.Bidder, &extAJA); err != nil { + if err := jsonutil.Unmarshal(extImp.Bidder, &extAJA); err != nil { return extAJA, &errortypes.BadInput{ Message: fmt.Sprintf("Failed to unmarshal ext.bidder impID: %s err: %s", imp.ID, err), } @@ -91,7 +93,7 @@ func (a *AJAAdapter) MakeBids(bidReq *openrtb2.BidRequest, adapterReq *adapters. } var bidResp openrtb2.BidResponse - if err := json.Unmarshal(adapterResp.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(adapterResp.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("Failed to unmarshal bid response: %s", err.Error()), }} diff --git a/adapters/aja/aja_test.go b/adapters/aja/aja_test.go index 75e35bedeb0..ab4165d09ab 100644 --- a/adapters/aja/aja_test.go +++ b/adapters/aja/aja_test.go @@ -3,9 +3,9 @@ package aja import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) const testsBidderEndpoint = "https://localhost/bid/4" diff --git a/adapters/aja/ajatest/exemplary/banner-multiple-imps.json b/adapters/aja/ajatest/exemplary/banner-multiple-imps.json index 8de9a31eadb..0dd5ba07ff7 100644 --- a/adapters/aja/ajatest/exemplary/banner-multiple-imps.json +++ b/adapters/aja/ajatest/exemplary/banner-multiple-imps.json @@ -54,7 +54,8 @@ "buyeruid": "test-uid" }, "tmax": 500 - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -98,7 +99,8 @@ "buyeruid": "test-uid" }, "tmax": 500 - } + }, + "impIDs":["test-imp-id2"] }, "mockResponse": { "status": 200, diff --git a/adapters/aja/ajatest/exemplary/video.json b/adapters/aja/ajatest/exemplary/video.json index a7991570bba..4e3774028a6 100644 --- a/adapters/aja/ajatest/exemplary/video.json +++ b/adapters/aja/ajatest/exemplary/video.json @@ -44,7 +44,8 @@ "buyeruid": "test-uid" }, "tmax": 500 - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/aja/ajatest/supplemental/invalid-bid-type.json b/adapters/aja/ajatest/supplemental/invalid-bid-type.json index 52f2c49296a..bcbabc1f1fd 100644 --- a/adapters/aja/ajatest/supplemental/invalid-bid-type.json +++ b/adapters/aja/ajatest/supplemental/invalid-bid-type.json @@ -34,7 +34,8 @@ "buyeruid": "test-uid" }, "tmax": 500 - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/aja/ajatest/supplemental/invalid-ext-bidder.json b/adapters/aja/ajatest/supplemental/invalid-ext-bidder.json index b12b431b0ed..54a774b3240 100644 --- a/adapters/aja/ajatest/supplemental/invalid-ext-bidder.json +++ b/adapters/aja/ajatest/supplemental/invalid-ext-bidder.json @@ -28,7 +28,7 @@ "expectedMakeRequestsErrors": [ { - "value": "Failed to unmarshal ext.bidder impID: test-imp-id err: json: cannot unmarshal number into Go struct field ExtImpAJA.asi of type string", + "value": "Failed to unmarshal ext.bidder impID: test-imp-id err: cannot unmarshal openrtb_ext.ExtImpAJA.AdSpotID: expects \" or n, but found 1", "comparison": "literal" } diff --git a/adapters/aja/ajatest/supplemental/invalid-ext.json b/adapters/aja/ajatest/supplemental/invalid-ext.json index 478222d0ee9..4562da9eea1 100644 --- a/adapters/aja/ajatest/supplemental/invalid-ext.json +++ b/adapters/aja/ajatest/supplemental/invalid-ext.json @@ -24,7 +24,7 @@ "expectedMakeRequestsErrors": [ { - "value": "Failed to unmarshal ext impID: test-imp-id err: json: cannot unmarshal number into Go value of type adapters.ExtImpBidder", + "value": "Failed to unmarshal ext impID: test-imp-id err: expect { or n, but found 1", "comparison": "literal" } diff --git a/adapters/aja/ajatest/supplemental/status-bad-request.json b/adapters/aja/ajatest/supplemental/status-bad-request.json index a47db8bbca9..7733fdd75b8 100644 --- a/adapters/aja/ajatest/supplemental/status-bad-request.json +++ b/adapters/aja/ajatest/supplemental/status-bad-request.json @@ -44,7 +44,8 @@ "buyeruid": "test-uid" }, "tmax": 500 - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/aja/ajatest/supplemental/status-internal-server-error.json b/adapters/aja/ajatest/supplemental/status-internal-server-error.json index 5d36dc5dcdc..56b0f437bd9 100644 --- a/adapters/aja/ajatest/supplemental/status-internal-server-error.json +++ b/adapters/aja/ajatest/supplemental/status-internal-server-error.json @@ -44,7 +44,8 @@ "buyeruid": "test-uid" }, "tmax": 500 - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 500, diff --git a/adapters/aja/ajatest/supplemental/status-no-content.json b/adapters/aja/ajatest/supplemental/status-no-content.json index e12fd21a26a..d21b1ab38d1 100644 --- a/adapters/aja/ajatest/supplemental/status-no-content.json +++ b/adapters/aja/ajatest/supplemental/status-no-content.json @@ -44,7 +44,8 @@ "buyeruid": "test-uid" }, "tmax": 500 - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/algorix/algorix.go b/adapters/algorix/algorix.go index 07f2b123389..f6848e58e3c 100644 --- a/adapters/algorix/algorix.go +++ b/adapters/algorix/algorix.go @@ -7,12 +7,13 @@ import ( "net/url" "text/template" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { @@ -80,6 +81,7 @@ func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestDa Uri: endPoint, Body: reqBody, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }, nil } @@ -87,11 +89,11 @@ func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestDa func getImpAlgoriXExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpAlgorix, error) { var extImpAlgoriX openrtb_ext.ExtImpAlgorix var extBidder adapters.ExtImpBidder - err := json.Unmarshal(imp.Ext, &extBidder) + err := jsonutil.Unmarshal(imp.Ext, &extBidder) if err != nil { return nil, err } - err = json.Unmarshal(extBidder.Bidder, &extImpAlgoriX) + err = jsonutil.Unmarshal(extBidder.Bidder, &extImpAlgoriX) if err != nil { return nil, err } @@ -133,7 +135,7 @@ func preProcess(request *openrtb2.BidRequest) { } if request.Imp[i].Video != nil { var impExt adapters.ExtImpBidder - err := json.Unmarshal(request.Imp[i].Ext, &impExt) + err := jsonutil.Unmarshal(request.Imp[i].Ext, &impExt) if err != nil { continue } @@ -141,6 +143,9 @@ func preProcess(request *openrtb2.BidRequest) { videoCopy := *request.Imp[i].Video videoExt := algorixVideoExt{Rewarded: 1} videoCopy.Ext, err = json.Marshal(&videoExt) + if err != nil { + continue + } request.Imp[i].Video = &videoCopy } } @@ -165,7 +170,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest } var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -191,7 +196,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest func getBidType(bid openrtb2.Bid, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { var bidExt algorixResponseBidExt - err := json.Unmarshal(bid.Ext, &bidExt) + err := jsonutil.Unmarshal(bid.Ext, &bidExt) if err == nil { switch bidExt.MediaType { case "banner": diff --git a/adapters/algorix/algorix_test.go b/adapters/algorix/algorix_test.go index 762b00dcee4..6b6d21e4d15 100644 --- a/adapters/algorix/algorix_test.go +++ b/adapters/algorix/algorix_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/algorix/algorixtest/exemplary/sample-banner-euc.json b/adapters/algorix/algorixtest/exemplary/sample-banner-euc.json index bc451225709..18102ed5048 100644 --- a/adapters/algorix/algorixtest/exemplary/sample-banner-euc.json +++ b/adapters/algorix/algorixtest/exemplary/sample-banner-euc.json @@ -41,7 +41,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/algorix/algorixtest/exemplary/sample-banner-use.json b/adapters/algorix/algorixtest/exemplary/sample-banner-use.json index ca83ee4e872..14a135f1142 100644 --- a/adapters/algorix/algorixtest/exemplary/sample-banner-use.json +++ b/adapters/algorix/algorixtest/exemplary/sample-banner-use.json @@ -41,7 +41,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/algorix/algorixtest/exemplary/sample-banner-with-mediatype.json b/adapters/algorix/algorixtest/exemplary/sample-banner-with-mediatype.json index c9f9f877cde..3a1d7286a5d 100644 --- a/adapters/algorix/algorixtest/exemplary/sample-banner-with-mediatype.json +++ b/adapters/algorix/algorixtest/exemplary/sample-banner-with-mediatype.json @@ -55,7 +55,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/algorix/algorixtest/exemplary/sample-banner-with-palcementid.json b/adapters/algorix/algorixtest/exemplary/sample-banner-with-palcementid.json index e50fb3a48e8..b26db6420ec 100644 --- a/adapters/algorix/algorixtest/exemplary/sample-banner-with-palcementid.json +++ b/adapters/algorix/algorixtest/exemplary/sample-banner-with-palcementid.json @@ -47,7 +47,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/algorix/algorixtest/exemplary/sample-banner.json b/adapters/algorix/algorixtest/exemplary/sample-banner.json index d558214a46d..a133c5bded8 100644 --- a/adapters/algorix/algorixtest/exemplary/sample-banner.json +++ b/adapters/algorix/algorixtest/exemplary/sample-banner.json @@ -41,7 +41,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/algorix/algorixtest/exemplary/sample-native.json b/adapters/algorix/algorixtest/exemplary/sample-native.json index 36a0d1af84f..dcfe15f45f5 100644 --- a/adapters/algorix/algorixtest/exemplary/sample-native.json +++ b/adapters/algorix/algorixtest/exemplary/sample-native.json @@ -42,7 +42,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/algorix/algorixtest/exemplary/sample-nobid.json b/adapters/algorix/algorixtest/exemplary/sample-nobid.json index d4fbd561f7e..4d7c4e49cd4 100644 --- a/adapters/algorix/algorixtest/exemplary/sample-nobid.json +++ b/adapters/algorix/algorixtest/exemplary/sample-nobid.json @@ -43,7 +43,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/algorix/algorixtest/exemplary/sample-rewarded-video.json b/adapters/algorix/algorixtest/exemplary/sample-rewarded-video.json index 0d98de60a8d..1925dc01b9a 100644 --- a/adapters/algorix/algorixtest/exemplary/sample-rewarded-video.json +++ b/adapters/algorix/algorixtest/exemplary/sample-rewarded-video.json @@ -58,7 +58,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/algorix/algorixtest/exemplary/sample-video.json b/adapters/algorix/algorixtest/exemplary/sample-video.json index edb8fddba09..f3ff4659aba 100644 --- a/adapters/algorix/algorixtest/exemplary/sample-video.json +++ b/adapters/algorix/algorixtest/exemplary/sample-video.json @@ -49,7 +49,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/algorix/algorixtest/supplemental/bad_response.json b/adapters/algorix/algorixtest/supplemental/bad_response.json index 83ed5663072..6a99f748df2 100644 --- a/adapters/algorix/algorixtest/supplemental/bad_response.json +++ b/adapters/algorix/algorixtest/supplemental/bad_response.json @@ -39,7 +39,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -51,7 +52,7 @@ "expectedMakeBidsErrors": [ { "comparison": "literal", - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse" + "value": "expect { or n, but found \"" } ] } diff --git a/adapters/algorix/algorixtest/supplemental/multiformat-no-mediatype-response.json b/adapters/algorix/algorixtest/supplemental/multiformat-no-mediatype-response.json index 4d3ea89454e..985d2d3a965 100644 --- a/adapters/algorix/algorixtest/supplemental/multiformat-no-mediatype-response.json +++ b/adapters/algorix/algorixtest/supplemental/multiformat-no-mediatype-response.json @@ -55,7 +55,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/algorix/algorixtest/supplemental/sample-banner-with-other-mediatype.json b/adapters/algorix/algorixtest/supplemental/sample-banner-with-other-mediatype.json index df53531856d..dfbb26ca1cb 100644 --- a/adapters/algorix/algorixtest/supplemental/sample-banner-with-other-mediatype.json +++ b/adapters/algorix/algorixtest/supplemental/sample-banner-with-other-mediatype.json @@ -41,7 +41,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/algorix/algorixtest/supplemental/status_400.json b/adapters/algorix/algorixtest/supplemental/status_400.json index 2085bf3cbee..bfec811d6a7 100644 --- a/adapters/algorix/algorixtest/supplemental/status_400.json +++ b/adapters/algorix/algorixtest/supplemental/status_400.json @@ -39,7 +39,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/algorix/algorixtest/supplemental/status_500.json b/adapters/algorix/algorixtest/supplemental/status_500.json index c7820917b28..1b6789591ce 100644 --- a/adapters/algorix/algorixtest/supplemental/status_500.json +++ b/adapters/algorix/algorixtest/supplemental/status_500.json @@ -39,7 +39,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 500, diff --git a/adapters/algorix/params_test.go b/adapters/algorix/params_test.go index 227b9e0a6d4..6b5570c9ad9 100644 --- a/adapters/algorix/params_test.go +++ b/adapters/algorix/params_test.go @@ -2,8 +2,9 @@ package algorix import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/alkimi/alkimi.go b/adapters/alkimi/alkimi.go new file mode 100644 index 00000000000..4f924bb15e6 --- /dev/null +++ b/adapters/alkimi/alkimi.go @@ -0,0 +1,195 @@ +package alkimi + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/floors" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +const price_macro = "${AUCTION_PRICE}" + +type adapter struct { + endpoint string +} + +type extObj struct { + AlkimiBidderExt openrtb_ext.ExtImpAlkimi `json:"bidder"` +} + +// Builder builds a new instance of the Alkimi adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + endpointURL, err := url.Parse(config.Endpoint) + if err != nil || len(endpointURL.String()) == 0 { + return nil, fmt.Errorf("invalid endpoint: %v", err) + } + + bidder := &adapter{ + endpoint: endpointURL.String(), + } + return bidder, nil +} + +// MakeRequests creates Alkimi adapter requests +func (adapter *adapter) MakeRequests(request *openrtb2.BidRequest, req *adapters.ExtraRequestInfo) (reqsBidder []*adapters.RequestData, errs []error) { + reqCopy := *request + + updated, errs := updateImps(reqCopy) + if len(errs) > 0 || len(reqCopy.Imp) != len(updated) { + return nil, errs + } + + reqCopy.Imp = updated + encoded, err := json.Marshal(reqCopy) + if err != nil { + errs = append(errs, err) + } else { + reqBidder := buildBidderRequest(adapter, encoded, openrtb_ext.GetImpIDs(reqCopy.Imp)) + reqsBidder = append(reqsBidder, reqBidder) + } + return +} + +func updateImps(bidRequest openrtb2.BidRequest) ([]openrtb2.Imp, []error) { + var errs []error + + updatedImps := make([]openrtb2.Imp, 0, len(bidRequest.Imp)) + for _, imp := range bidRequest.Imp { + + var bidderExt adapters.ExtImpBidder + var extImpAlkimi openrtb_ext.ExtImpAlkimi + + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { + errs = append(errs, err) + continue + } + + if err := jsonutil.Unmarshal(bidderExt.Bidder, &extImpAlkimi); err != nil { + errs = append(errs, err) + continue + } + + var bidFloorPrice floors.Price + bidFloorPrice.FloorMinCur = imp.BidFloorCur + bidFloorPrice.FloorMin = imp.BidFloor + + if len(bidFloorPrice.FloorMinCur) > 0 && bidFloorPrice.FloorMin > 0 { + imp.BidFloor = bidFloorPrice.FloorMin + } else { + imp.BidFloor = extImpAlkimi.BidFloor + } + imp.Instl = extImpAlkimi.Instl + imp.Exp = extImpAlkimi.Exp + + temp := extObj{AlkimiBidderExt: extImpAlkimi} + temp.AlkimiBidderExt.AdUnitCode = imp.ID + + extJson, err := json.Marshal(temp) + if err != nil { + errs = append(errs, err) + continue + } + imp.Ext = extJson + updatedImps = append(updatedImps, imp) + } + + return updatedImps, errs +} + +func buildBidderRequest(adapter *adapter, encoded []byte, impIDs []string) *adapters.RequestData { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + reqBidder := &adapters.RequestData{ + Method: "POST", + Uri: adapter.endpoint, + Body: encoded, + Headers: headers, + ImpIDs: impIDs, + } + return reqBidder +} + +// MakeBids will parse the bids from the Alkimi server +func (adapter *adapter) MakeBids(request *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var errs []error + + if adapters.IsResponseStatusCodeNoContent(response) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil { + return nil, []error{err} + } + + var bidResp openrtb2.BidResponse + err := jsonutil.Unmarshal(response.Body, &bidResp) + if err != nil { + return nil, []error{err} + } + + seatBidCount := len(bidResp.SeatBid) + if seatBidCount == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty SeatBid array", + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + for _, seatBid := range bidResp.SeatBid { + for _, bid := range seatBid.Bid { + copyBid := bid + resolveMacros(©Bid) + impId := copyBid.ImpID + imp := request.Imp + bidType, err := getMediaTypeForImp(impId, imp) + if err != nil { + errs = append(errs, err) + continue + } + bidderBid := &adapters.TypedBid{ + Bid: ©Bid, + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, bidderBid) + } + } + return bidResponse, errs +} + +func resolveMacros(bid *openrtb2.Bid) { + strPrice := strconv.FormatFloat(bid.Price, 'f', -1, 64) + bid.NURL = strings.Replace(bid.NURL, price_macro, strPrice, -1) + bid.AdM = strings.Replace(bid.AdM, price_macro, strPrice, -1) +} + +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { + for _, imp := range imps { + if imp.ID == impId { + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } + if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } + if imp.Audio != nil { + return openrtb_ext.BidTypeAudio, nil + } + } + } + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find imp \"%s\"", impId), + } +} diff --git a/adapters/alkimi/alkimi_test.go b/adapters/alkimi/alkimi_test.go new file mode 100644 index 00000000000..65f296a58e6 --- /dev/null +++ b/adapters/alkimi/alkimi_test.go @@ -0,0 +1,57 @@ +package alkimi + +import ( + "testing" + + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +const ( + alkimiTestEndpoint = "https://exchange.alkimi-onboarding.com/server/bid" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder( + openrtb_ext.BidderAlkimi, + config.Adapter{Endpoint: alkimiTestEndpoint}, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}, + ) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "alkimitest", bidder) +} + +func TestEndpointEmpty(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderAlkimi, config.Adapter{ + Endpoint: ""}, config.Server{ExternalUrl: alkimiTestEndpoint, GvlID: 1, DataCenter: "2"}) + assert.Error(t, buildErr) +} + +func TestEndpointMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderAlkimi, config.Adapter{ + Endpoint: " http://leading.space.is.invalid"}, config.Server{ExternalUrl: alkimiTestEndpoint, GvlID: 1, DataCenter: "2"}) + assert.Error(t, buildErr) +} + +func TestBuilder(t *testing.T) { + bidder, buildErr := buildBidder() + if buildErr != nil { + t.Fatalf("Failed to build bidder: %v", buildErr) + } + assert.NotNil(t, bidder) +} + +func buildBidder() (adapters.Bidder, error) { + return Builder( + openrtb_ext.BidderAlkimi, + config.Adapter{Endpoint: alkimiTestEndpoint}, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}, + ) +} diff --git a/adapters/alkimi/alkimitest/exemplary/simple-audio.json b/adapters/alkimi/alkimitest/exemplary/simple-audio.json new file mode 100644 index 00000000000..2ce94161cbe --- /dev/null +++ b/adapters/alkimi/alkimitest/exemplary/simple-audio.json @@ -0,0 +1,144 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "audio": { + "mimes": [ + "audio/mpeg", + "audio/mp3" + ], + "minduration": 5, + "maxduration": 30, + "minbitrate": 32, + "maxbitrate": 128 + }, + "bidfloor": 0.7, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "token": "XXX", + "bidFloor": 0.5 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://exchange.alkimi-onboarding.com/server/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "audio": { + "mimes": [ + "audio/mpeg", + "audio/mp3" + ], + "minduration": 5, + "maxduration": 30, + "minbitrate": 32, + "maxbitrate": 128 + }, + "bidfloor": 0.7, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "token": "XXX", + "bidFloor": 0.5, + "adUnitCode": "test-imp-id", + "exp": 0, + "instl": 0 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.9, + "adm": "<\/Error><\/Impression>00:00:15<\/Duration><\/Tracking><\/TrackingEvents><\/ClickThrough><\/VideoClicks><\/MediaFile><\/MediaFiles><\/Linear><\/Creative><\/Creatives><\/InLine><\/Ad><\/VAST>", + "cid": "test_cid", + "crid": "test_crid", + "ext": { + "prebid": { + "type": "audio" + } + } + } + ], + "seat": "alkimi" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.9, + "adm": "<\/Error><\/Impression>00:00:15<\/Duration><\/Tracking><\/TrackingEvents><\/ClickThrough><\/VideoClicks><\/MediaFile><\/MediaFiles><\/Linear><\/Creative><\/Creatives><\/InLine><\/Ad><\/VAST>", + "cid": "test_cid", + "crid": "test_crid", + "ext": { + "prebid": { + "type": "audio" + } + } + }, + "type": "audio" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/alkimi/alkimitest/exemplary/simple-banner.json b/adapters/alkimi/alkimitest/exemplary/simple-banner.json new file mode 100644 index 00000000000..dea656368a9 --- /dev/null +++ b/adapters/alkimi/alkimitest/exemplary/simple-banner.json @@ -0,0 +1,151 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "token": "XXX", + "bidFloor": 0.5 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://exchange.alkimi-onboarding.com/server/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloor": 0.5, + "ext": { + "bidder": { + "token": "XXX", + "bidFloor": 0.5, + "adUnitCode": "test-imp-id", + "exp": 0, + "instl": 0 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.9, + "adm": "
<\/a><\/div>", + "adomain": [ + "sonobi.com" + ], + "cid": "house", + "crid": "sandbox", + "h": 1, + "w": 1 + } + ], + "seat": "sonobi" + } + ], + "bidid": "sandbox_642305097", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "some-impression-id", + "price": 2.8649999999999998, + "adm": "", + "adomain": [ + "sonobi.com" + ], + "cid": "house", + "crid": "sandbox", + "h": 1, + "w": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/sovrn/sovrn.go b/adapters/sovrn/sovrn.go index 15b07800b78..ea28891883d 100644 --- a/adapters/sovrn/sovrn.go +++ b/adapters/sovrn/sovrn.go @@ -8,12 +8,13 @@ import ( "strconv" "strings" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" ) type SovrnAdapter struct { @@ -50,7 +51,7 @@ func (s *SovrnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapt for _, imp := range request.Imp { var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { errs = append(errs, &errortypes.BadInput{ Message: err.Error(), }) @@ -58,7 +59,7 @@ func (s *SovrnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapt } var sovrnExt openrtb_ext.ExtImpSovrn - if err := json.Unmarshal(bidderExt.Bidder, &sovrnExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &sovrnExt); err != nil { errs = append(errs, &errortypes.BadInput{ Message: err.Error(), }) @@ -126,6 +127,7 @@ func (s *SovrnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapt Uri: s.URI, Body: reqJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }}, errs } @@ -153,7 +155,7 @@ func (s *SovrnAdapter) MakeBids(request *openrtb2.BidRequest, bidderRequest *ada } var bidResponse openrtb2.BidResponse - if err := json.Unmarshal(bidderResponse.Body, &bidResponse); err != nil { + if err := jsonutil.Unmarshal(bidderResponse.Body, &bidResponse); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: err.Error(), }} diff --git a/adapters/sovrn/sovrn_test.go b/adapters/sovrn/sovrn_test.go index 4a382d9b58e..f2d61d166e2 100644 --- a/adapters/sovrn/sovrn_test.go +++ b/adapters/sovrn/sovrn_test.go @@ -3,9 +3,9 @@ package sovrn import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/sovrn/sovrntest/exemplary/multi-banner.json b/adapters/sovrn/sovrntest/exemplary/multi-banner.json index 495941039a1..b6978a082a0 100644 --- a/adapters/sovrn/sovrntest/exemplary/multi-banner.json +++ b/adapters/sovrn/sovrntest/exemplary/multi-banner.json @@ -129,7 +129,8 @@ "dnt": 0, "language": "en" } - } + }, + "impIDs":["test-imp-id1","test-imp-id2"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/exemplary/no-bid.json b/adapters/sovrn/sovrntest/exemplary/no-bid.json index bad14cf1b05..9edacbbaa05 100644 --- a/adapters/sovrn/sovrntest/exemplary/no-bid.json +++ b/adapters/sovrn/sovrntest/exemplary/no-bid.json @@ -103,7 +103,8 @@ "dnt": 0, "language": "en" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/exemplary/simple-banner.json b/adapters/sovrn/sovrntest/exemplary/simple-banner.json index cd088ba12e4..e8d6a9832d7 100644 --- a/adapters/sovrn/sovrntest/exemplary/simple-banner.json +++ b/adapters/sovrn/sovrntest/exemplary/simple-banner.json @@ -88,7 +88,8 @@ "dnt": 0, "language": "en" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/supplemental/adunitcode.json b/adapters/sovrn/sovrntest/supplemental/adunitcode.json index 4719752058d..a4b64e4394d 100644 --- a/adapters/sovrn/sovrntest/supplemental/adunitcode.json +++ b/adapters/sovrn/sovrntest/supplemental/adunitcode.json @@ -77,7 +77,8 @@ "buyeruid": "test_reader_id" }, "device": { } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/supplemental/blank-device.json b/adapters/sovrn/sovrntest/supplemental/blank-device.json index 0aa3ad74e62..5cda47dfa06 100644 --- a/adapters/sovrn/sovrntest/supplemental/blank-device.json +++ b/adapters/sovrn/sovrntest/supplemental/blank-device.json @@ -74,7 +74,8 @@ "buyeruid": "test_reader_id" }, "device": { } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json index 4b997b68266..0621450b112 100644 --- a/adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json +++ b/adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json @@ -78,7 +78,8 @@ "buyeruid": "test_reader_id" }, "device": { } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/supplemental/camel-case-tagId.json b/adapters/sovrn/sovrntest/supplemental/camel-case-tagId.json index 40029b12c53..d195dddc528 100644 --- a/adapters/sovrn/sovrntest/supplemental/camel-case-tagId.json +++ b/adapters/sovrn/sovrntest/supplemental/camel-case-tagId.json @@ -88,7 +88,8 @@ "dnt": 0, "language": "en" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/supplemental/fpd.json b/adapters/sovrn/sovrntest/supplemental/fpd.json index e4368c13c2b..c9b56703f81 100644 --- a/adapters/sovrn/sovrntest/supplemental/fpd.json +++ b/adapters/sovrn/sovrntest/supplemental/fpd.json @@ -119,7 +119,8 @@ "dnt": 0, "language": "en" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/supplemental/gdpr.json b/adapters/sovrn/sovrntest/supplemental/gdpr.json index a699ff1ae9c..242db2627bb 100644 --- a/adapters/sovrn/sovrntest/supplemental/gdpr.json +++ b/adapters/sovrn/sovrntest/supplemental/gdpr.json @@ -104,7 +104,8 @@ "gdpr": 1 } } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/supplemental/invalid-adunitcode.json b/adapters/sovrn/sovrntest/supplemental/invalid-adunitcode.json index fdb14371336..444bba21523 100644 --- a/adapters/sovrn/sovrntest/supplemental/invalid-adunitcode.json +++ b/adapters/sovrn/sovrntest/supplemental/invalid-adunitcode.json @@ -75,7 +75,8 @@ "buyeruid": "test_reader_id" }, "device": { } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/supplemental/invalid-imp-id.json b/adapters/sovrn/sovrntest/supplemental/invalid-imp-id.json index e3b2c17c8c3..4cfe70d08e4 100644 --- a/adapters/sovrn/sovrntest/supplemental/invalid-imp-id.json +++ b/adapters/sovrn/sovrntest/supplemental/invalid-imp-id.json @@ -88,7 +88,8 @@ "dnt": 0, "language": "en" } - } + }, + "impIDs":["test-imp-id1"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/supplemental/no-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/no-bidfloor.json index 0aa3ad74e62..5cda47dfa06 100644 --- a/adapters/sovrn/sovrntest/supplemental/no-bidfloor.json +++ b/adapters/sovrn/sovrntest/supplemental/no-bidfloor.json @@ -74,7 +74,8 @@ "buyeruid": "test_reader_id" }, "device": { } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/supplemental/no-user.json b/adapters/sovrn/sovrntest/supplemental/no-user.json index f9ba82bfd21..f03eb6b3604 100644 --- a/adapters/sovrn/sovrntest/supplemental/no-user.json +++ b/adapters/sovrn/sovrntest/supplemental/no-user.json @@ -81,7 +81,8 @@ "ip": "123.123.123.123", "language": "en" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json index 20c00a48bd2..cf3edbfe34a 100644 --- a/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json +++ b/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json @@ -77,7 +77,8 @@ "buyeruid": "test_reader_id" }, "device": { } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json index cb74e5643b6..87470ee06bc 100644 --- a/adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json +++ b/adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json @@ -76,7 +76,8 @@ "buyeruid": "test_reader_id" }, "device": { } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/supplemental/with-both-custom-default-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/with-both-custom-default-bidfloor.json index 4b997b68266..0621450b112 100644 --- a/adapters/sovrn/sovrntest/supplemental/with-both-custom-default-bidfloor.json +++ b/adapters/sovrn/sovrntest/supplemental/with-both-custom-default-bidfloor.json @@ -78,7 +78,8 @@ "buyeruid": "test_reader_id" }, "device": { } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/supplemental/with-only-custom-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/with-only-custom-bidfloor.json index b4ab404e9be..0b986e7de7d 100644 --- a/adapters/sovrn/sovrntest/supplemental/with-only-custom-bidfloor.json +++ b/adapters/sovrn/sovrntest/supplemental/with-only-custom-bidfloor.json @@ -77,7 +77,8 @@ "buyeruid": "test_reader_id" }, "device": { } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/supplemental/with-only-default-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/with-only-default-bidfloor.json index cb74e5643b6..87470ee06bc 100644 --- a/adapters/sovrn/sovrntest/supplemental/with-only-default-bidfloor.json +++ b/adapters/sovrn/sovrntest/supplemental/with-only-default-bidfloor.json @@ -76,7 +76,8 @@ "buyeruid": "test_reader_id" }, "device": { } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/video/full-video.json b/adapters/sovrn/sovrntest/video/full-video.json index 18f277ea97f..13e1d3dadd7 100644 --- a/adapters/sovrn/sovrntest/video/full-video.json +++ b/adapters/sovrn/sovrntest/video/full-video.json @@ -146,7 +146,8 @@ "dnt": 0, "language": "en" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/video/multi-banner-video.json b/adapters/sovrn/sovrntest/video/multi-banner-video.json index abfa1ab37bc..0462ad15944 100644 --- a/adapters/sovrn/sovrntest/video/multi-banner-video.json +++ b/adapters/sovrn/sovrntest/video/multi-banner-video.json @@ -136,7 +136,8 @@ "dnt": 0, "language": "en" } - } + }, + "impIDs":["test-imp-id1","test-imp-id2"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/video/multi-video.json b/adapters/sovrn/sovrntest/video/multi-video.json index 5fdcadcc37f..b1db51859ca 100644 --- a/adapters/sovrn/sovrntest/video/multi-video.json +++ b/adapters/sovrn/sovrntest/video/multi-video.json @@ -155,7 +155,8 @@ "dnt": 0, "language": "en" } - } + }, + "impIDs":["test-imp-id1","test-imp-id2"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/video/no-bid.json b/adapters/sovrn/sovrntest/video/no-bid.json index 7c9bc4c7eff..0cdb299263c 100644 --- a/adapters/sovrn/sovrntest/video/no-bid.json +++ b/adapters/sovrn/sovrntest/video/no-bid.json @@ -109,7 +109,8 @@ "dnt": 0, "language": "en" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrn/sovrntest/video/simple-video.json b/adapters/sovrn/sovrntest/video/simple-video.json index fae6778a015..08343909319 100644 --- a/adapters/sovrn/sovrntest/video/simple-video.json +++ b/adapters/sovrn/sovrntest/video/simple-video.json @@ -107,7 +107,8 @@ "dnt": 0, "language": "en" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/sovrnXsp/params_test.go b/adapters/sovrnXsp/params_test.go new file mode 100644 index 00000000000..91d31ec54dc --- /dev/null +++ b/adapters/sovrnXsp/params_test.go @@ -0,0 +1,56 @@ +package sovrnXsp + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + + if err != nil { + t.Fatalf("Failed to fetch json-schemas. %v", err) + } + + for _, param := range validParams { + if err := validator.Validate(openrtb_ext.BidderSovrnXsp, json.RawMessage(param)); err != nil { + t.Errorf("Schema rejected sovrnXsp params: %s", param) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + + if err != nil { + t.Fatalf("Failed to fetch json-schemas. %v", err) + } + + for _, param := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderSovrnXsp, json.RawMessage(param)); err == nil { + t.Errorf("Schema allowed sovrnXsp params: %s", param) + } + } +} + +var validParams = []string{ + `{"pub_id":"1234"}`, + `{"pub_id":"1234","med_id":"1234"}`, + `{"pub_id":"1234","med_id":"1234","zone_id":"abcdefghijklmnopqrstuvwxyz"}`, + `{"pub_id":"1234","med_id":"1234","zone_id":"abcdefghijklmnopqrstuvwxyz","force_bid":true}`, + `{"pub_id":"1234","med_id":"1234","zone_id":"abcdefghijklmnopqrstuvwxyz","force_bid":false}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `0`, + `[]`, + `{}`, + `{"pub_id":""}`, + `{"pub_id":"123"}`, + `{"pub_id":"1234","zone_id":"123"}`, +} diff --git a/adapters/sovrnXsp/sovrnXsp.go b/adapters/sovrnXsp/sovrnXsp.go new file mode 100644 index 00000000000..7dc90adfd7f --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsp.go @@ -0,0 +1,174 @@ +package sovrnXsp + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" + + "github.com/prebid/openrtb/v20/openrtb2" +) + +type adapter struct { + Endpoint string +} + +// bidExt.CreativeType values. +const ( + creativeTypeBanner int = 0 + creativeTypeVideo int = 1 + creativeTypeNative int = 2 + creativeTypeAudio int = 3 +) + +// Bid response extension from XSP. +type bidExt struct { + CreativeType int `json:"creative_type"` +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + appCopy := *request.App + if appCopy.Publisher == nil { + appCopy.Publisher = &openrtb2.Publisher{} + } else { + publisherCopy := *appCopy.Publisher + appCopy.Publisher = &publisherCopy + } + request.App = &appCopy + + var errors []error + var imps []openrtb2.Imp + + for idx, imp := range request.Imp { + if imp.Banner == nil && imp.Video == nil && imp.Native == nil { + continue + } + + var bidderExt adapters.ExtImpBidder + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { + err = &errortypes.BadInput{ + Message: fmt.Sprintf("imp #%d: ext.bidder not provided", idx), + } + errors = append(errors, err) + continue + } + + var xspExt openrtb_ext.ExtImpSovrnXsp + if err := jsonutil.Unmarshal(bidderExt.Bidder, &xspExt); err != nil { + err = &errortypes.BadInput{ + Message: fmt.Sprintf("imp #%d: %s", idx, err.Error()), + } + errors = append(errors, err) + continue + } + + request.App.Publisher.ID = xspExt.PubID + if xspExt.MedID != "" { + request.App.ID = xspExt.MedID + } + if xspExt.ZoneID != "" { + imp.TagID = xspExt.ZoneID + } + imps = append(imps, imp) + } + + if len(imps) == 0 { + return nil, append(errors, &errortypes.BadInput{ + Message: "no matching impression with ad format", + }) + } + + request.Imp = imps + requestJson, err := json.Marshal(request) + if err != nil { + return nil, append(errors, err) + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("x-openrtb-version", "2.5") + + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.Endpoint, + Body: requestJson, + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), + }}, errors +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + var errors []error + result := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + + for _, seatBid := range response.SeatBid { + for _, bid := range seatBid.Bid { + bid := bid + var ext bidExt + if err := jsonutil.Unmarshal(bid.Ext, &ext); err != nil { + errors = append(errors, err) + continue + } + + var bidType openrtb_ext.BidType + var mkupType openrtb2.MarkupType + switch ext.CreativeType { + case creativeTypeBanner: + bidType = openrtb_ext.BidTypeBanner + mkupType = openrtb2.MarkupBanner + case creativeTypeVideo: + bidType = openrtb_ext.BidTypeVideo + mkupType = openrtb2.MarkupVideo + case creativeTypeNative: + bidType = openrtb_ext.BidTypeNative + mkupType = openrtb2.MarkupNative + default: + errors = append(errors, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unsupported creative type: %d", ext.CreativeType), + }) + continue + } + + if bid.MType == 0 { + bid.MType = mkupType + } + + result.Bids = append(result.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + }) + } + } + + if len(result.Bids) == 0 { + // it's possible an empty seat array was sent as a response + return nil, errors + } + return result, errors +} + +// Builder builds a new instance of the SovrnXSP adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + Endpoint: config.Endpoint, + } + return bidder, nil +} diff --git a/adapters/sovrnXsp/sovrnXsp_test.go b/adapters/sovrnXsp/sovrnXsp_test.go new file mode 100644 index 00000000000..3a82e29a54c --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsp_test.go @@ -0,0 +1,20 @@ +package sovrnXsp + +import ( + "testing" + + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderSovrnXsp, config.Adapter{ + Endpoint: "http://xsp.lijit.com/json/rtb/prebid/server"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "sovrnXsptest", bidder) +} diff --git a/adapters/sovrnXsp/sovrnXsptest/exemplary/banner.json b/adapters/sovrnXsp/sovrnXsptest/exemplary/banner.json new file mode 100644 index 00000000000..96ad9e6d8f5 --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsptest/exemplary/banner.json @@ -0,0 +1,120 @@ +{ + "mockBidRequest": { + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [ + { + "id": "imp123", + "bidfloor": 1.0, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://xsp.lijit.com/json/rtb/prebid/server", + "body":{ + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [{ + "id": "imp123", + "tagid": "FgUtQqop18uf1I2fwDie", + "bidfloor": 1.0, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + }] + }, + "impIDs":["imp123"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "abc", + "seatbid": [ + { + "bid": [ + { + "adm": "", + "crid": "test_banner_crid", + "cid": "test_cid", + "impid": "imp123", + "id": "1", + "price": 1.0, + "ext": { + "creative_type": 0 + } + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "", + "crid": "test_banner_crid", + "cid": "test_cid", + "impid": "imp123", + "price": 1.0, + "id": "1", + "mtype": 1, + "ext": { + "creative_type": 0 + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/sovrnXsp/sovrnXsptest/exemplary/native.json b/adapters/sovrnXsp/sovrnXsptest/exemplary/native.json new file mode 100644 index 00000000000..ed0a98709be --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsptest/exemplary/native.json @@ -0,0 +1,110 @@ +{ + "mockBidRequest": { + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [ + { + "id": "imp123", + "bidfloor": 1.0, + "native": { + "ver": "1.2", + "request": "{\"context\":1,\"plcmttype\":1,\"assets\":[{\"id\":0,\"required\":1,\"img\":{\"type\":3,\"w\":300,\"h\":250}},{\"id\":1,\"required\":1,\"title\":{\"len\":140}},{\"id\":2,\"data\":{\"type\":1}}],\"eventtrackers\":[{\"event\":1,\"methods\":[1]}]}" + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://xsp.lijit.com/json/rtb/prebid/server", + "body":{ + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [{ + "id": "imp123", + "tagid": "FgUtQqop18uf1I2fwDie", + "bidfloor": 1.0, + "native": { + "ver": "1.2", + "request": "{\"context\":1,\"plcmttype\":1,\"assets\":[{\"id\":0,\"required\":1,\"img\":{\"type\":3,\"w\":300,\"h\":250}},{\"id\":1,\"required\":1,\"title\":{\"len\":140}},{\"id\":2,\"data\":{\"type\":1}}],\"eventtrackers\":[{\"event\":1,\"methods\":[1]}]}" + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + }] + }, + "impIDs":["imp123"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "abc", + "seatbid": [ + { + "bid": [ + { + "adm": "{\"ver\":\"1.2\",\"link\":{\"url\":\"https://sovrn.com\"},\"assets\":[{\"id\":0,\"img\":{\"w\":300,\"h\":250,\"url\":\"https://ads.smrtb.com/demo/ads/300x250.png\"}},{\"id\":1,\"title\":{\"text\":\"Test Ad\",\"len\":7}},{\"id\":2,\"data\":{\"value\":\"0\",\"len\":1}}]}", + "crid": "test_native_crid", + "cid": "test_cid", + "impid": "imp123", + "id": "1", + "price": 1.0, + "ext": { + "creative_type": 2 + } + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "{\"ver\":\"1.2\",\"link\":{\"url\":\"https://sovrn.com\"},\"assets\":[{\"id\":0,\"img\":{\"w\":300,\"h\":250,\"url\":\"https://ads.smrtb.com/demo/ads/300x250.png\"}},{\"id\":1,\"title\":{\"text\":\"Test Ad\",\"len\":7}},{\"id\":2,\"data\":{\"value\":\"0\",\"len\":1}}]}", + "crid": "test_native_crid", + "cid": "test_cid", + "impid": "imp123", + "price": 1.0, + "id": "1", + "mtype": 4, + "ext": { + "creative_type": 2 + } + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/sovrnXsp/sovrnXsptest/exemplary/video.json b/adapters/sovrnXsp/sovrnXsptest/exemplary/video.json new file mode 100644 index 00000000000..b47f47b8779 --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsptest/exemplary/video.json @@ -0,0 +1,122 @@ +{ + "mockBidRequest": { + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [ + { + "id": "imp123", + "bidfloor": 1.0, + "video": { + "w": 300, + "h": 250, + "protocols": [1,2,3,4,5,6,7,8], + "playbackmethod": [1], + "mimes": ["video/mp4"], + "skip": 1, + "api": [2], + "maxbitrate": 3000 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://xsp.lijit.com/json/rtb/prebid/server", + "body":{ + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [{ + "id": "imp123", + "bidfloor": 1.0, + "tagid": "FgUtQqop18uf1I2fwDie", + "video": { + "w": 300, + "h": 250, + "protocols": [1,2,3,4,5,6,7,8], + "playbackmethod": [1], + "mimes": ["video/mp4"], + "skip": 1, + "api": [2], + "maxbitrate": 3000 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + }] + }, + "impIDs":["imp123"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "abc", + "seatbid": [ + { + "bid": [ + { + "adm": "Mc0Sovrn PSA00:00:30", + "crid": "sovrn_psa_crid_1", + "cid": "sovrn_psa", + "impid": "imp123", + "id": "1", + "price": 1.0, + "ext": { + "creative_type": 1 + } + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "Mc0Sovrn PSA00:00:30", + "crid": "sovrn_psa_crid_1", + "cid": "sovrn_psa", + "impid": "imp123", + "price": 1.0, + "id": "1", + "mtype": 2, + "ext": { + "creative_type": 1 + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/sovrnXsp/sovrnXsptest/supplemental/request-no-matching-imp.json b/adapters/sovrnXsp/sovrnXsptest/supplemental/request-no-matching-imp.json new file mode 100644 index 00000000000..d9719d0a2e1 --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsptest/supplemental/request-no-matching-imp.json @@ -0,0 +1,30 @@ +{ + "mockBidRequest": { + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [ + { + "id": "imp123", + "bidfloor": 1.0, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [{ + "value": "no matching impression with ad format", + "comparison": "literal" + }] +} diff --git a/adapters/sovrnXsp/sovrnXsptest/supplemental/response-empty-seat.json b/adapters/sovrnXsp/sovrnXsptest/supplemental/response-empty-seat.json new file mode 100644 index 00000000000..7aa9983bfc3 --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsptest/supplemental/response-empty-seat.json @@ -0,0 +1,83 @@ +{ + "mockBidRequest": { + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [ + { + "id": "imp123", + "bidfloor": 1.0, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://xsp.lijit.com/json/rtb/prebid/server", + "body":{ + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [{ + "id": "imp123", + "tagid": "FgUtQqop18uf1I2fwDie", + "bidfloor": 1.0, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + }] + }, + "impIDs":["imp123"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "abc", + "seatbid": [{}] + } + } + } + ], + + "expectedBidResponses": [] +} diff --git a/adapters/sovrnXsp/sovrnXsptest/supplemental/response-http-error.json b/adapters/sovrnXsp/sovrnXsptest/supplemental/response-http-error.json new file mode 100644 index 00000000000..285f3418795 --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsptest/supplemental/response-http-error.json @@ -0,0 +1,85 @@ +{ + "mockBidRequest": { + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [ + { + "id": "imp123", + "bidfloor": 1.0, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://xsp.lijit.com/json/rtb/prebid/server", + "body":{ + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [{ + "id": "imp123", + "tagid": "FgUtQqop18uf1I2fwDie", + "bidfloor": 1.0, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + }] + }, + "impIDs":["imp123"] + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + + "expectedBidResponses": [], + + "expectedMakeBidsErrors": [{ + "value": "Unexpected status code: 500", + "comparison": "regex" + }] +} diff --git a/adapters/sovrnXsp/sovrnXsptest/supplemental/response-invalid-crtype.json b/adapters/sovrnXsp/sovrnXsptest/supplemental/response-invalid-crtype.json new file mode 100644 index 00000000000..d48c2b6cba6 --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsptest/supplemental/response-invalid-crtype.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [ + { + "id": "imp123", + "bidfloor": 1.0, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://xsp.lijit.com/json/rtb/prebid/server", + "body":{ + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [{ + "id": "imp123", + "tagid": "FgUtQqop18uf1I2fwDie", + "bidfloor": 1.0, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + }] + }, + "impIDs":["imp123"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "abc", + "seatbid": [ + { + "bid": [ + { + "adm": "", + "crid": "test_banner_crid", + "cid": "test_cid", + "impid": "imp123", + "id": "1", + "price": 1.0, + "ext": { + "creative_type": 100 + } + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [], + + "expectedMakeBidsErrors": [{ + "value": "Unsupported creative type: 100", + "comparison": "literal" + }] +} diff --git a/adapters/sovrnXsp/sovrnXsptest/supplemental/response-nobid.json b/adapters/sovrnXsp/sovrnXsptest/supplemental/response-nobid.json new file mode 100644 index 00000000000..072fea60115 --- /dev/null +++ b/adapters/sovrnXsp/sovrnXsptest/supplemental/response-nobid.json @@ -0,0 +1,80 @@ +{ + "mockBidRequest": { + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [ + { + "id": "imp123", + "bidfloor": 1.0, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://xsp.lijit.com/json/rtb/prebid/server", + "body":{ + "id": "abc", + "app": { + "id": "0jqCuiqHfPDHAHrdFfGG", + "bundle": "test", + "publisher": { + "id": "sovrn" + } + }, + "imp": [{ + "id": "imp123", + "tagid": "FgUtQqop18uf1I2fwDie", + "bidfloor": 1.0, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pub_id": "sovrn", + "med_id": "0jqCuiqHfPDHAHrdFfGG", + "zone_id": "FgUtQqop18uf1I2fwDie" + } + } + }] + }, + "impIDs":["imp123"] + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + + "expectedBidResponses": [] +} diff --git a/adapters/sspBC/sspbc.go b/adapters/sspBC/sspbc.go index 6b601c120e0..ae7d5f40e14 100644 --- a/adapters/sspBC/sspbc.go +++ b/adapters/sspBC/sspbc.go @@ -10,11 +10,12 @@ import ( "net/url" "strings" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) const ( @@ -128,6 +129,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte Method: http.MethodPost, Uri: requestURL.String(), Body: requestJSON, + ImpIDs: getImpIDs(formattedRequest.Imp), } return []*adapters.RequestData{requestData}, nil @@ -146,7 +148,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest } var response openrtb2.BidResponse - if err := json.Unmarshal(externalResponse.Body, &response); err != nil { + if err := jsonutil.Unmarshal(externalResponse.Body, &response); err != nil { return nil, []error{err} } @@ -191,7 +193,7 @@ func (a *adapter) impToBid(internalRequest *openrtb2.BidRequest, seatBid openrtb // read additional data from proxy var bidDataExt responseExt - if err := json.Unmarshal(bid.Ext, &bidDataExt); err != nil { + if err := jsonutil.Unmarshal(bid.Ext, &bidDataExt); err != nil { return err } /* @@ -288,8 +290,8 @@ func getBidParameters(imp openrtb2.Imp) openrtb_ext.ExtImpSspbc { var extBidder adapters.ExtImpBidder var extSSP openrtb_ext.ExtImpSspbc - if err := json.Unmarshal(imp.Ext, &extBidder); err == nil { - _ = json.Unmarshal(extBidder.Bidder, &extSSP) + if err := jsonutil.Unmarshal(imp.Ext, &extBidder); err == nil { + _ = jsonutil.Unmarshal(extBidder.Bidder, &extSSP) } return extSSP @@ -396,3 +398,12 @@ func formatSspBcRequest(request *openrtb2.BidRequest) (*openrtb2.BidRequest, err return request, nil } + +// getImpIDs uses imp.TagID instead of imp.ID as formattedRequest stores imp.ID in imp.TagID +func getImpIDs(imps []openrtb2.Imp) []string { + impIDs := make([]string, len(imps)) + for i := range imps { + impIDs[i] = imps[i].TagID + } + return impIDs +} diff --git a/adapters/sspBC/sspbc_test.go b/adapters/sspBC/sspbc_test.go index 47a8ae5d183..2d709f4c463 100644 --- a/adapters/sspBC/sspbc_test.go +++ b/adapters/sspBC/sspbc_test.go @@ -3,9 +3,9 @@ package sspBC import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/sspBC/sspbctest/exemplary/banner-fromtemplate.json b/adapters/sspBC/sspbctest/exemplary/banner-fromtemplate.json index 4d54443f2a9..a8af9496124 100644 --- a/adapters/sspBC/sspbctest/exemplary/banner-fromtemplate.json +++ b/adapters/sspBC/sspbctest/exemplary/banner-fromtemplate.json @@ -82,7 +82,8 @@ "gdpr": 1 } } - } + }, + "impIDs":["slot"] }, "mockResponse": { "status": 200, diff --git a/adapters/sspBC/sspbctest/exemplary/banner-preformatted-multiple-imps.json b/adapters/sspBC/sspbctest/exemplary/banner-preformatted-multiple-imps.json index ab958148abc..15856d4ae4a 100644 --- a/adapters/sspBC/sspbctest/exemplary/banner-preformatted-multiple-imps.json +++ b/adapters/sspBC/sspbctest/exemplary/banner-preformatted-multiple-imps.json @@ -119,7 +119,8 @@ "gdpr": 1 } } - } + }, + "impIDs":["slot1","slot2"] }, "mockResponse": { "status": 200, diff --git a/adapters/sspBC/sspbctest/exemplary/banner-preformatted-onecode.json b/adapters/sspBC/sspbctest/exemplary/banner-preformatted-onecode.json index aaf9a7b4968..c700d9e584e 100644 --- a/adapters/sspBC/sspbctest/exemplary/banner-preformatted-onecode.json +++ b/adapters/sspBC/sspbctest/exemplary/banner-preformatted-onecode.json @@ -76,7 +76,8 @@ "gdpr": 1 } } - } + }, + "impIDs":["slot"] }, "mockResponse": { "status": 200, diff --git a/adapters/sspBC/sspbctest/exemplary/banner-preformatted.json b/adapters/sspBC/sspbctest/exemplary/banner-preformatted.json index d5c3125e443..1a9b22e7801 100644 --- a/adapters/sspBC/sspbctest/exemplary/banner-preformatted.json +++ b/adapters/sspBC/sspbctest/exemplary/banner-preformatted.json @@ -82,7 +82,8 @@ "gdpr": 1 } } - } + }, + "impIDs":["slot"] }, "mockResponse": { "status": 200, diff --git a/adapters/sspBC/sspbctest/supplemental/bad_response.json b/adapters/sspBC/sspbctest/supplemental/bad_response.json index 4e67b182730..14f7d4ddb11 100644 --- a/adapters/sspBC/sspbctest/supplemental/bad_response.json +++ b/adapters/sspBC/sspbctest/supplemental/bad_response.json @@ -82,7 +82,8 @@ "gdpr": 1 } } - } + }, + "impIDs":["slot"] }, "mockResponse": { "status": 200, @@ -92,7 +93,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/sspBC/sspbctest/supplemental/bad_response_with_incorrect_impid.json b/adapters/sspBC/sspbctest/supplemental/bad_response_with_incorrect_impid.json index bc5ffa205c2..26c9e42cf6c 100644 --- a/adapters/sspBC/sspbctest/supplemental/bad_response_with_incorrect_impid.json +++ b/adapters/sspBC/sspbctest/supplemental/bad_response_with_incorrect_impid.json @@ -82,7 +82,8 @@ "gdpr": 1 } } - } + }, + "impIDs":["slot"] }, "mockResponse": { "status": 200, diff --git a/adapters/sspBC/sspbctest/supplemental/bad_response_without_adm.json b/adapters/sspBC/sspbctest/supplemental/bad_response_without_adm.json index f83584999ab..f975b66d2cf 100644 --- a/adapters/sspBC/sspbctest/supplemental/bad_response_without_adm.json +++ b/adapters/sspBC/sspbctest/supplemental/bad_response_without_adm.json @@ -82,7 +82,8 @@ "gdpr": 1 } } - } + }, + "impIDs":["slot"] }, "mockResponse": { "status": 200, diff --git a/adapters/sspBC/sspbctest/supplemental/request_with_diffrent_siteid.json b/adapters/sspBC/sspbctest/supplemental/request_with_diffrent_siteid.json index 26a5043bbef..92a77fa8405 100644 --- a/adapters/sspBC/sspbctest/supplemental/request_with_diffrent_siteid.json +++ b/adapters/sspBC/sspbctest/supplemental/request_with_diffrent_siteid.json @@ -83,7 +83,8 @@ "gdpr": 1 } } - } + }, + "impIDs":["slot"] }, "mockResponse": { "status": 200, diff --git a/adapters/sspBC/sspbctest/supplemental/request_with_incorrect_imp_bidder_ext.json b/adapters/sspBC/sspbctest/supplemental/request_with_incorrect_imp_bidder_ext.json index f9017c197ac..8e089cfefda 100644 --- a/adapters/sspBC/sspbctest/supplemental/request_with_incorrect_imp_bidder_ext.json +++ b/adapters/sspBC/sspbctest/supplemental/request_with_incorrect_imp_bidder_ext.json @@ -78,7 +78,8 @@ "gdpr": 1 } } - } + }, + "impIDs":["slot"] }, "mockResponse": { "status": 200, diff --git a/adapters/sspBC/sspbctest/supplemental/request_with_incorrect_imp_ext.json b/adapters/sspBC/sspbctest/supplemental/request_with_incorrect_imp_ext.json index c26dfa35b6c..3ab3f998ba9 100644 --- a/adapters/sspBC/sspbctest/supplemental/request_with_incorrect_imp_ext.json +++ b/adapters/sspBC/sspbctest/supplemental/request_with_incorrect_imp_ext.json @@ -76,7 +76,8 @@ "gdpr": 1 } } - } + }, + "impIDs":["slot"] }, "mockResponse": { "status": 200, diff --git a/adapters/sspBC/sspbctest/supplemental/request_with_standard_and_onecode_imp.json b/adapters/sspBC/sspbctest/supplemental/request_with_standard_and_onecode_imp.json index a304aabe768..7c864f8c9a5 100644 --- a/adapters/sspBC/sspbctest/supplemental/request_with_standard_and_onecode_imp.json +++ b/adapters/sspBC/sspbctest/supplemental/request_with_standard_and_onecode_imp.json @@ -113,7 +113,8 @@ "gdpr": 1 } } - } + }, + "impIDs":["slot-standard","slot-onecode"] }, "mockResponse": { "status": 200, diff --git a/adapters/sspBC/sspbctest/supplemental/request_with_test.json b/adapters/sspBC/sspbctest/supplemental/request_with_test.json index a30b77e70c9..70f740b4b6c 100644 --- a/adapters/sspBC/sspbctest/supplemental/request_with_test.json +++ b/adapters/sspBC/sspbctest/supplemental/request_with_test.json @@ -85,7 +85,8 @@ "gdpr": 1 } } - } + }, + "impIDs":["slot"] }, "mockResponse": { "status": 200, diff --git a/adapters/sspBC/sspbctest/supplemental/request_without_banner_format.json b/adapters/sspBC/sspbctest/supplemental/request_without_banner_format.json index 39322ae4b06..7572f88cacd 100644 --- a/adapters/sspBC/sspbctest/supplemental/request_without_banner_format.json +++ b/adapters/sspBC/sspbctest/supplemental/request_without_banner_format.json @@ -71,7 +71,8 @@ "gdpr": 1 } } - } + }, + "impIDs":["slot"] }, "mockResponse": { "status": 200, diff --git a/adapters/sspBC/sspbctest/supplemental/request_without_ext_id.json b/adapters/sspBC/sspbctest/supplemental/request_without_ext_id.json index d6d92952a95..f7692a91b0e 100644 --- a/adapters/sspBC/sspbctest/supplemental/request_without_ext_id.json +++ b/adapters/sspBC/sspbctest/supplemental/request_without_ext_id.json @@ -80,7 +80,8 @@ "gdpr": 1 } } - } + }, + "impIDs":["slot"] }, "mockResponse": { "status": 200, diff --git a/adapters/sspBC/sspbctest/supplemental/request_without_ext_site_id.json b/adapters/sspBC/sspbctest/supplemental/request_without_ext_site_id.json index 6d5858345df..eb04d2f7c72 100644 --- a/adapters/sspBC/sspbctest/supplemental/request_without_ext_site_id.json +++ b/adapters/sspBC/sspbctest/supplemental/request_without_ext_site_id.json @@ -81,7 +81,8 @@ "gdpr": 1 } } - } + }, + "impIDs":["slot"] }, "mockResponse": { "status": 200, diff --git a/adapters/sspBC/sspbctest/supplemental/status_204.json b/adapters/sspBC/sspbctest/supplemental/status_204.json index 931f59978bc..f26421a6356 100644 --- a/adapters/sspBC/sspbctest/supplemental/status_204.json +++ b/adapters/sspBC/sspbctest/supplemental/status_204.json @@ -83,7 +83,8 @@ "gdpr": 1 } } - } + }, + "impIDs":["slot"] }, "mockResponse": { "status": 204, diff --git a/adapters/sspBC/sspbctest/supplemental/status_400.json b/adapters/sspBC/sspbctest/supplemental/status_400.json index 75a763b3a8e..adf79df5d22 100644 --- a/adapters/sspBC/sspbctest/supplemental/status_400.json +++ b/adapters/sspBC/sspbctest/supplemental/status_400.json @@ -83,7 +83,8 @@ "gdpr": 1 } } - } + }, + "impIDs":["slot"] }, "mockResponse": { "status": 400, diff --git a/adapters/stroeerCore/params_test.go b/adapters/stroeerCore/params_test.go index 92586189b6f..4050a219599 100644 --- a/adapters/stroeerCore/params_test.go +++ b/adapters/stroeerCore/params_test.go @@ -2,8 +2,9 @@ package stroeerCore import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/stroeerCore/stroeercore.go b/adapters/stroeerCore/stroeercore.go index 08ba83a9544..1d503726eb3 100644 --- a/adapters/stroeerCore/stroeercore.go +++ b/adapters/stroeerCore/stroeercore.go @@ -5,12 +5,13 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { @@ -23,13 +24,30 @@ type response struct { } type bidResponse struct { - ID string `json:"id"` - BidID string `json:"bidId"` - CPM float64 `json:"cpm"` - Width int64 `json:"width"` - Height int64 `json:"height"` - Ad string `json:"ad"` - CrID string `json:"crid"` + ID string `json:"id"` + BidID string `json:"bidId"` + CPM float64 `json:"cpm"` + Width int64 `json:"width"` + Height int64 `json:"height"` + Ad string `json:"ad"` + CrID string `json:"crid"` + Mtype string `json:"mtype"` + DSA json.RawMessage `json:"dsa"` +} + +type bidExt struct { + DSA json.RawMessage `json:"dsa,omitempty"` +} + +func (b *bidResponse) resolveMediaType() (mt openrtb2.MarkupType, bt openrtb_ext.BidType, err error) { + switch b.Mtype { + case "banner": + return openrtb2.MarkupBanner, openrtb_ext.BidTypeBanner, nil + case "video": + return openrtb2.MarkupVideo, openrtb_ext.BidTypeVideo, nil + default: + return mt, bt, fmt.Errorf("unable to determine media type for bid with id \"%s\"", b.BidID) + } } func (a *adapter) MakeBids(bidRequest *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { @@ -42,7 +60,7 @@ func (a *adapter) MakeBids(bidRequest *openrtb2.BidRequest, requestData *adapter var errors []error stroeerResponse := response{} - if err := json.Unmarshal(responseData.Body, &stroeerResponse); err != nil { + if err := jsonutil.Unmarshal(responseData.Body, &stroeerResponse); err != nil { errors = append(errors, err) return nil, errors } @@ -51,6 +69,14 @@ func (a *adapter) MakeBids(bidRequest *openrtb2.BidRequest, requestData *adapter bidderResponse.Currency = "EUR" for _, bid := range stroeerResponse.Bids { + markupType, bidType, err := bid.resolveMediaType() + if err != nil { + errors = append(errors, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bid media type error: %s", err.Error()), + }) + continue + } + openRtbBid := openrtb2.Bid{ ID: bid.ID, ImpID: bid.BidID, @@ -59,11 +85,21 @@ func (a *adapter) MakeBids(bidRequest *openrtb2.BidRequest, requestData *adapter Price: bid.CPM, AdM: bid.Ad, CrID: bid.CrID, + MType: markupType, + } + + if bid.DSA != nil { + dsaJson, err := json.Marshal(bidExt{bid.DSA}) + if err != nil { + errors = append(errors, err) + } else { + openRtbBid.Ext = dsaJson + } } bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ Bid: &openRtbBid, - BidType: openrtb_ext.BidTypeBanner, + BidType: bidType, }) } @@ -76,13 +112,13 @@ func (a *adapter) MakeRequests(bidRequest *openrtb2.BidRequest, extraRequestInfo for idx := range bidRequest.Imp { imp := &bidRequest.Imp[idx] var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { errors = append(errors, err) continue } var stroeerExt openrtb_ext.ExtImpStroeerCore - if err := json.Unmarshal(bidderExt.Bidder, &stroeerExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &stroeerExt); err != nil { errors = append(errors, err) continue } @@ -105,6 +141,7 @@ func (a *adapter) MakeRequests(bidRequest *openrtb2.BidRequest, extraRequestInfo Uri: a.URL, Body: reqJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(bidRequest.Imp), }}, errors } diff --git a/adapters/stroeerCore/stroeercore_test.go b/adapters/stroeerCore/stroeercore_test.go index fc7a680add0..b0f6204880c 100644 --- a/adapters/stroeerCore/stroeercore_test.go +++ b/adapters/stroeerCore/stroeercore_test.go @@ -3,9 +3,9 @@ package stroeerCore import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/stroeerCore/stroeercoretest/exemplary/dsa.json b/adapters/stroeerCore/stroeercoretest/exemplary/dsa.json new file mode 100644 index 00000000000..97355da9053 --- /dev/null +++ b/adapters/stroeerCore/stroeercoretest/exemplary/dsa.json @@ -0,0 +1,203 @@ +{ + "mockBidRequest": { + "id": "auction-id", + "cur": [ + "EUR" + ], + "imp": [ + { + "id": "1", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + } + ], + "device": { + "ip": "123.123.123.123" + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/some/path" + }, + "user": { + "buyeruid": "test-buyer-user-id" + }, + "regs": { + "ext": { + "dsa": { + "dsarequired": 3, + "pubrender": 0, + "datatopub": 2, + "transparency": [ + { + "domain": "example-platform.com", + "dsaparams": [ + 1 + ] + }, + { + "domain": "example-ssp.com", + "dsaparams": [ + 1, + 2 + ] + } + ] + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "http://localhost/s2sdsh", + "body": { + "id": "auction-id", + "cur": [ + "EUR" + ], + "imp": [ + { + "id": "1", + "tagid": "123456", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + } + ], + "device": { + "ip": "123.123.123.123" + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/some/path" + }, + "user": { + "buyeruid": "test-buyer-user-id" + }, + "regs": { + "ext": { + "dsa": { + "dsarequired": 3, + "pubrender": 0, + "datatopub": 2, + "transparency": [ + { + "domain": "example-platform.com", + "dsaparams": [ + 1 + ] + }, + { + "domain": "example-ssp.com", + "dsaparams": [ + 1, + 2 + ] + } + ] + } + } + } + }, + "impIDs":["1"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "bids": [ + { + "id": "1829273982920-01", + "bidId": "1", + "cpm": 2, + "width": 300, + "height": 600, + "ad": "advert", + "crid": "wasd", + "mtype": "banner", + "dsa": { + "behalf": "Advertiser A", + "paid": "Advertiser B", + "transparency": [ + { + "domain": "example-domain.com", + "dsaparams": [ + 1, + 2 + ] + } + ], + "adrender": 1 + } + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "1829273982920-01", + "impid": "1", + "price": 2, + "adm": "advert", + "w": 300, + "h": 600, + "crid": "wasd", + "mtype": 1, + "ext": { + "dsa": { + "behalf": "Advertiser A", + "paid": "Advertiser B", + "transparency": [ + { + "domain": "example-domain.com", + "dsaparams": [ + 1, + 2 + ] + } + ], + "adrender": 1 + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/stroeerCore/stroeercoretest/exemplary/mobile-banner-single.json b/adapters/stroeerCore/stroeercoretest/exemplary/mobile-banner-single.json index 0abdbc8b499..3cff0a2842f 100644 --- a/adapters/stroeerCore/stroeercoretest/exemplary/mobile-banner-single.json +++ b/adapters/stroeerCore/stroeercoretest/exemplary/mobile-banner-single.json @@ -147,7 +147,8 @@ "user": { "buyeruid": "test-buyer-user-id" } - } + }, + "impIDs":["3"] }, "mockResponse": { "status": 200, @@ -161,7 +162,8 @@ "width": 468, "height": 60, "ad": "advert", - "crid": "XYZijk" + "crid": "XYZijk", + "mtype": "banner" } ] } @@ -179,7 +181,8 @@ "adm": "advert", "w": 468, "h": 60, - "crid": "XYZijk" + "crid": "XYZijk", + "mtype": 1 }, "type": "banner" }] diff --git a/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-multi.json b/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-multi.json index 091f244c6ef..ad6e4d5f373 100644 --- a/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-multi.json +++ b/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-multi.json @@ -124,7 +124,8 @@ "user": { "buyeruid": "test-buyer-user-id" } - } + }, + "impIDs":["3","9"] }, "mockResponse": { "status": 200, @@ -138,7 +139,8 @@ "width": 468, "height": 60, "ad": "advert", - "crid": "qwert" + "crid": "qwert", + "mtype": "banner" }, { "id": "3929239282-02", @@ -147,7 +149,8 @@ "width": 770, "height": 250, "ad": "another advert", - "crid": "wasd" + "crid": "wasd", + "mtype": "banner" } ] } @@ -166,7 +169,8 @@ "adm": "advert", "w": 468, "h": 60, - "crid": "qwert" + "crid": "qwert", + "mtype": 1 }, "type": "banner" }, @@ -178,7 +182,8 @@ "adm": "another advert", "w": 770, "h": 250, - "crid": "wasd" + "crid": "wasd", + "mtype": 1 }, "type": "banner" } diff --git a/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-single.json b/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-single.json index ca6ea0c26ea..1cc339732ff 100644 --- a/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-single.json +++ b/adapters/stroeerCore/stroeercoretest/exemplary/site-banner-single.json @@ -91,7 +91,8 @@ "user": { "buyeruid": "test-buyer-user-id" } - } + }, + "impIDs":["3"] }, "mockResponse": { "status": 200, @@ -105,7 +106,8 @@ "width": 468, "height": 60, "ad": "advert", - "crid": "wasd" + "crid": "wasd", + "mtype": "banner" } ] } @@ -123,7 +125,8 @@ "adm": "advert", "w": 468, "h": 60, - "crid": "wasd" + "crid": "wasd", + "mtype": 1 }, "type": "banner" }] diff --git a/adapters/stroeerCore/stroeercoretest/exemplary/site-multi-format-single.json b/adapters/stroeerCore/stroeercoretest/exemplary/site-multi-format-single.json new file mode 100644 index 00000000000..b87aead0c61 --- /dev/null +++ b/adapters/stroeerCore/stroeercoretest/exemplary/site-multi-format-single.json @@ -0,0 +1,147 @@ +{ + "mockBidRequest": { + "id": "auction-id", + "cur": ["EUR"], + "imp": [ + { + "id": "3", + "banner": { + "format": [ + { + "w": 468, + "h": 60 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/some/path", + "ext": { + "amp": 1 + } + }, + "user": { + "buyeruid": "test-buyer-user-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"] + }, + "uri": "http://localhost/s2sdsh", + "body": { + "id": "auction-id", + "cur": ["EUR"], + "imp": [ + { + "id": "3", + "tagid": "123456", + "banner": { + "format": [ + { + "w": 468, + "h": 60 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "video": { + "mimes": ["video/mp4"], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/some/path", + "ext": { + "amp": 1 + } + }, + "user": { + "buyeruid": "test-buyer-user-id" + } + }, + "impIDs":["3"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "bids": [ + { + "id": "3929239282-01", + "bidId": "3", + "cpm": 2, + "width": 468, + "height": 60, + "ad": "banner ad", + "crid": "qwert", + "mtype": "banner" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "3929239282-01", + "impid": "3", + "price": 2, + "adm": "banner ad", + "w": 468, + "h": 60, + "crid": "qwert", + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/stroeerCore/stroeercoretest/exemplary/site-multi-types.json b/adapters/stroeerCore/stroeercoretest/exemplary/site-multi-types.json new file mode 100644 index 00000000000..71bcec598c1 --- /dev/null +++ b/adapters/stroeerCore/stroeercoretest/exemplary/site-multi-types.json @@ -0,0 +1,187 @@ +{ + "mockBidRequest": { + "id": "auction-id", + "cur": ["EUR"], + "imp": [ + { + "id": "3", + "banner": { + "format": [ + { + "w": 468, + "h": 60 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + }, + { + "id": "9", + "video": { + "mimes": ["video/mp4"], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "sid": "85310" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/some/path", + "ext": { + "amp": 1 + } + }, + "user": { + "buyeruid": "test-buyer-user-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"] + }, + "uri": "http://localhost/s2sdsh", + "body": { + "id": "auction-id", + "cur": ["EUR"], + "imp": [ + { + "id": "3", + "tagid": "123456", + "banner": { + "format": [ + { + "w": 468, + "h": 60 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + }, + { + "id": "9", + "tagid": "85310", + "video": { + "mimes": ["video/mp4"], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "sid": "85310" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/some/path", + "ext": { + "amp": 1 + } + }, + "user": { + "buyeruid": "test-buyer-user-id" + } + }, + "impIDs":["3","9"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "bids": [ + { + "id": "3929239282-01", + "bidId": "3", + "cpm": 2, + "width": 468, + "height": 60, + "ad": "banner ad", + "crid": "qwert", + "mtype": "banner" + }, + { + "id": "3929239282-02", + "bidId": "9", + "cpm": 7.21, + "width": 770, + "height": 250, + "ad": "video ad", + "crid": "wasd", + "mtype": "video" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "3929239282-01", + "impid": "3", + "price": 2, + "adm": "banner ad", + "w": 468, + "h": 60, + "crid": "qwert", + "mtype": 1 + }, + "type": "banner" + }, + { + "bid": { + "id": "3929239282-02", + "impid": "9", + "price": 7.21, + "adm": "video ad", + "w": 770, + "h": 250, + "crid": "wasd", + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/stroeerCore/stroeercoretest/exemplary/site-video-single.json b/adapters/stroeerCore/stroeercoretest/exemplary/site-video-single.json new file mode 100644 index 00000000000..b78fbcbe444 --- /dev/null +++ b/adapters/stroeerCore/stroeercoretest/exemplary/site-video-single.json @@ -0,0 +1,117 @@ +{ + "mockBidRequest": { + "id": "auction-id", + "cur": ["EUR"], + "imp": [ + { + "id": "3", + "video": { + "mimes": ["video/mp4"], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/some/path", + "ext": { + "amp": 0 + } + }, + "user": { + "buyeruid": "test-buyer-user-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"] + }, + "uri": "http://localhost/s2sdsh", + "body": { + "id": "auction-id", + "cur": ["EUR"], + "imp": [ + { + "id": "3", + "tagid": "123456", + "video": { + "mimes": ["video/mp4"], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "sid": "123456" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/some/path", + "ext": { + "amp": 0 + } + }, + "user": { + "buyeruid": "test-buyer-user-id" + } + }, + "impIDs":["3"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "bids": [ + { + "id": "8923356982838-09", + "bidId": "3", + "cpm": 2, + "ad": "
video
", + "crid": "wasd", + "mtype": "video" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids" : [{ + "bid": { + "id": "8923356982838-09", + "impid": "3", + "price": 2, + "adm": "
video
", + "crid": "wasd", + "mtype": 2 + }, + "type": "video" + }] + } + ] +} diff --git a/adapters/stroeerCore/stroeercoretest/supplemental/bad-server-response.json b/adapters/stroeerCore/stroeercoretest/supplemental/bad-server-response.json index 48275916b2e..b499d8c9f91 100644 --- a/adapters/stroeerCore/stroeercoretest/supplemental/bad-server-response.json +++ b/adapters/stroeerCore/stroeercoretest/supplemental/bad-server-response.json @@ -43,7 +43,8 @@ "user": { "buyeruid": "test-buyer-user-id" } - } + }, + "impIDs":["id"] }, "mockResponse": { "status": 500 diff --git a/adapters/stroeerCore/stroeercoretest/supplemental/unknown-bid-media-type.json b/adapters/stroeerCore/stroeercoretest/supplemental/unknown-bid-media-type.json new file mode 100644 index 00000000000..868b6782c86 --- /dev/null +++ b/adapters/stroeerCore/stroeercoretest/supplemental/unknown-bid-media-type.json @@ -0,0 +1,194 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "banner-1", + "banner": { + "format": [ + { + "w": 300, + "h": 200 + } + ] + }, + "ext": { + "bidder": { + "sid": "tagid" + } + } + }, + { + "id": "banner-2", + "banner": { + "format": [ + { + "w": 300, + "h": 200 + } + ] + }, + "ext": { + "bidder": { + "sid": "tagid" + } + } + }, + { + "id": "banner-3", + "banner": { + "format": [ + { + "w": 300, + "h": 200 + } + ] + }, + "ext": { + "bidder": { + "sid": "tagid" + } + } + } + ], + "user": { + "buyeruid": "test-buyer-user-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": ["application/json"], + "Content-Type": ["application/json;charset=utf-8"] + }, + "uri": "http://localhost/s2sdsh", + "body": { + "id": "id", + "imp": [ + { + "id": "banner-1", + "tagid": "tagid", + "banner": { + "format": [ + { + "w": 300, + "h": 200 + } + ] + }, + "ext": { + "bidder": { + "sid": "tagid" + } + } + }, + { + "id": "banner-2", + "tagid": "tagid", + "banner": { + "format": [ + { + "w": 300, + "h": 200 + } + ] + }, + "ext": { + "bidder": { + "sid": "tagid" + } + } + }, + { + "id": "banner-3", + "tagid": "tagid", + "banner": { + "format": [ + { + "w": 300, + "h": 200 + } + ] + }, + "ext": { + "bidder": { + "sid": "tagid" + } + } + } + ], + "user": { + "buyeruid": "test-buyer-user-id" + } + }, + "impIDs":["banner-1","banner-2","banner-3"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "bids": [ + { + "id": "3929239282-01", + "bidId": "banner-1", + "cpm": 2, + "width": 300, + "height": 200, + "ad": "banner ad 1", + "crid": "qwert", + "mtype": "unknown" + }, + { + "id": "3929239282-02", + "bidId": "banner-2", + "cpm": 2, + "width": 300, + "height": 200, + "ad": "banner ad 2", + "crid": "qwert" + }, + { + "id": "3929239282-03", + "bidId": "banner-3", + "cpm": 2, + "width": 300, + "height": 200, + "ad": "banner ad 3", + "crid": "qwert", + "mtype": "banner" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids" : [{ + "bid": { + "id": "3929239282-03", + "impid": "banner-3", + "price": 2, + "adm": "banner ad 3", + "w": 300, + "h": 200, + "crid": "qwert", + "mtype": 1 + }, + "type": "banner" + }] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bid media type error: unable to determine media type for bid with id \"banner-1\"", + "comparison": "literal" + }, + { + "value": "Bid media type error: unable to determine media type for bid with id \"banner-2\"", + "comparison": "literal" + } + ] +} diff --git a/adapters/suntContent/suntContent.go b/adapters/suntContent/suntContent.go deleted file mode 100644 index f5440737bd9..00000000000 --- a/adapters/suntContent/suntContent.go +++ /dev/null @@ -1,145 +0,0 @@ -package suntContent - -import ( - "encoding/json" - "fmt" - "net/http" - "strconv" - "strings" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" -) - -type adapter struct { - endpoint string -} - -func Builder(_ openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { - bidder := &adapter{ - endpoint: config.Endpoint, - } - return bidder, nil -} - -func (a *adapter) MakeRequests(request *openrtb2.BidRequest, extraRequestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - for i := range request.Imp { - if err := addTagID(&request.Imp[i]); err != nil { - return nil, []error{err} - } - } - - if !curExists(request.Cur, "EUR") { - request.Cur = append(request.Cur, "EUR") - } - - requestJSON, err := json.Marshal(request) - if err != nil { - return nil, []error{err} - } - - requestData := &adapters.RequestData{ - Method: http.MethodPost, - Uri: a.endpoint, - Body: requestJSON, - } - - return []*adapters.RequestData{requestData}, nil -} - -func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { - if responseData.StatusCode == http.StatusNoContent { - return nil, nil - } - - if responseData.StatusCode == http.StatusBadRequest { - err := &errortypes.BadInput{ - Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", - } - return nil, []error{err} - } - - if responseData.StatusCode != http.StatusOK { - err := &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode), - } - return nil, []error{err} - } - - var response openrtb2.BidResponse - if err := json.Unmarshal(responseData.Body, &response); err != nil { - return nil, []error{err} - } - - bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) - bidResponse.Currency = response.Cur - - var errs []error - - for _, seatBid := range response.SeatBid { - for i := range seatBid.Bid { - resolvePriceMacro(&seatBid.Bid[i]) - - bidType, err := getMediaTypeForBid(seatBid.Bid[i].Ext) - if err != nil { - errs = append(errs, err) - continue - } - - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &seatBid.Bid[i], - BidType: bidType, - }) - } - } - - return bidResponse, errs -} - -func resolvePriceMacro(bid *openrtb2.Bid) { - price := strconv.FormatFloat(bid.Price, 'f', -1, 64) - bid.AdM = strings.Replace(bid.AdM, "${AUCTION_PRICE}", price, -1) -} - -func getMediaTypeForBid(ext json.RawMessage) (openrtb_ext.BidType, error) { - var bidExt openrtb_ext.ExtBid - - if err := json.Unmarshal(ext, &bidExt); err != nil { - return "", fmt.Errorf("could not unmarshal openrtb_ext.ExtBid: %w", err) - } - - if bidExt.Prebid == nil { - return "", fmt.Errorf("bid.Ext.Prebid is empty") - } - - return openrtb_ext.ParseBidType(string(bidExt.Prebid.Type)) -} - -func curExists(allowedCurrencies []string, newCurrency string) bool { - for i := range allowedCurrencies { - if allowedCurrencies[i] == newCurrency { - return true - } - } - return false -} - -func addTagID(imp *openrtb2.Imp) error { - var ext adapters.ExtImpBidder - var extSA openrtb_ext.ImpExtSuntContent - - if err := json.Unmarshal(imp.Ext, &ext); err != nil { - return fmt.Errorf("could not unmarshal adapters.ExtImpBidder: %w", err) - } - - if err := json.Unmarshal(ext.Bidder, &extSA); err != nil { - return fmt.Errorf("could not unmarshal openrtb_ext.ImpExtSuntContent: %w", err) - } - - imp.TagID = extSA.AdUnitID - - return nil -} diff --git a/adapters/suntContent/suntContent_test.go b/adapters/suntContent/suntContent_test.go deleted file mode 100644 index 52be77efb60..00000000000 --- a/adapters/suntContent/suntContent_test.go +++ /dev/null @@ -1,194 +0,0 @@ -package suntContent - -import ( - "encoding/json" - "testing" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/stretchr/testify/assert" -) - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderSuntContent, config.Adapter{ - Endpoint: "https://mockup.suntcontent.com/", - }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "suntContenttest", bidder) -} - -func TestResolvePriceMacro(t *testing.T) { - adm := `{"link":{"url":"https://some_url.com/abc123?wp=${AUCTION_PRICE}"}` - want := `{"link":{"url":"https://some_url.com/abc123?wp=12.34"}` - - bid := openrtb2.Bid{AdM: adm, Price: 12.34} - resolvePriceMacro(&bid) - - if bid.AdM != want { - t.Fatalf("want: %v, got: %v", want, bid.AdM) - } -} - -func TestGetMediaTypeForBid(t *testing.T) { - tests := []struct { - name string - want openrtb_ext.BidType - invalidJSON bool - wantErr bool - wantErrContain string - bidType openrtb_ext.BidType - }{ - { - name: "native", - want: openrtb_ext.BidTypeNative, - invalidJSON: false, - wantErr: false, - wantErrContain: "", - bidType: "native", - }, - { - name: "banner", - want: openrtb_ext.BidTypeBanner, - invalidJSON: false, - wantErr: false, - wantErrContain: "", - bidType: "banner", - }, - { - name: "video", - want: openrtb_ext.BidTypeVideo, - invalidJSON: false, - wantErr: false, - wantErrContain: "", - bidType: "video", - }, - { - name: "audio", - want: openrtb_ext.BidTypeAudio, - invalidJSON: false, - wantErr: false, - wantErrContain: "", - bidType: "audio", - }, - { - name: "empty type", - want: "", - invalidJSON: false, - wantErr: true, - wantErrContain: "invalid BidType", - bidType: "", - }, - { - name: "invalid type", - want: "", - invalidJSON: false, - wantErr: true, - wantErrContain: "invalid BidType", - bidType: "invalid", - }, - { - name: "invalid json", - want: "", - invalidJSON: true, - wantErr: true, - wantErrContain: "bid.Ext.Prebid is empty", - bidType: "", - }, - } - - for _, test := range tests { - var bid openrtb2.SeatBid - var extBid openrtb_ext.ExtBid - - var bidExtJsonString string - if test.invalidJSON { - bidExtJsonString = `{"x_prebid": {"type":""}}` - } else { - bidExtJsonString = `{"prebid": {"type":"` + string(test.bidType) + `"}}` - } - - if err := bid.Ext.UnmarshalJSON([]byte(bidExtJsonString)); err != nil { - t.Fatalf("unexpected error %v", err) - } - - if err := json.Unmarshal(bid.Ext, &extBid); err != nil { - t.Fatalf("could not unmarshal extBid: %v", err) - } - - got, gotErr := getMediaTypeForBid(bid.Ext) - assert.Equal(t, test.want, got) - - if test.wantErr { - if gotErr != nil { - assert.Contains(t, gotErr.Error(), test.wantErrContain) - continue - } - t.Fatalf("wantErr: %v, gotErr: %v", test.wantErr, gotErr) - } - } -} - -func TestAddTagID(t *testing.T) { - tests := []struct { - name string - want string - data string - wantErr bool - }{ - {"regular case", "abc123", "abc123", false}, - {"nil case", "", "", false}, - {"unmarshal err case", "", "", true}, - } - - for _, test := range tests { - extSA, err := json.Marshal(openrtb_ext.ImpExtSuntContent{AdUnitID: test.data}) - if err != nil { - t.Fatalf("unexpected error %v", err) - } - - extBidder, err := json.Marshal(adapters.ExtImpBidder{Bidder: extSA}) - if err != nil { - t.Fatalf("unexpected error %v", err) - } - - if test.wantErr { - extBidder = []byte{} - } - - ortbImp := openrtb2.Imp{Ext: extBidder} - - if err := addTagID(&ortbImp); err != nil { - if test.wantErr { - continue - } - t.Fatalf("unexpected error %v", err) - } - - if test.want != ortbImp.TagID { - t.Fatalf("want: %v, got: %v", test.want, ortbImp.TagID) - } - } -} - -func TestCurExists(t *testing.T) { - tests := []struct { - name string - cur string - data []string - want bool - }{ - {"no eur", "EUR", []string{"USD"}, false}, - {"eur exists", "EUR", []string{"USD", "EUR"}, true}, - } - - for _, test := range tests { - got := curExists(test.data, test.cur) - assert.Equal(t, test.want, got) - } -} diff --git a/adapters/suntContent/suntContenttest/exemplary/native.json b/adapters/suntContent/suntContenttest/exemplary/native.json deleted file mode 100644 index 89ef4e07dfb..00000000000 --- a/adapters/suntContent/suntContenttest/exemplary/native.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "publisher": { - "id": "foo", - "name": "foo" - } - }, - "cur": [ - "EUR" - ], - "imp": [ - { - "id": "test-imp-id", - "native": { - "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" - }, - "ext": { - "bidder": { - "adUnitId": "example-tag-id" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://mockup.suntcontent.com/", - "body": { - "cur": [ - "EUR" - ], - "id": "test-request-id", - "imp": [ - { - "tagid": "example-tag-id", - "id": "test-imp-id", - "native": { - "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" - }, - "ext": { - "bidder": { - "adUnitId": "example-tag-id" - } - } - } - ], - "site": { - "publisher": { - "id": "foo", - "name": "foo" - } - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "some-id", - "impid": "test-imp-id", - "price": 1, - "adid": "69595837", - "adm": "{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 3000,\"h\": 2250}},{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 3,\"data\":{\"value\":\"Prebid.org\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}}],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\"],\"jstracker\":\"\"}", - "ext": { - "prebid": { - "type": "native" - } - } - } - ], - "seat": "123" - } - ], - "bidid": "8141327771600527856", - "cur": "EUR" - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "EUR", - "bids": [ - { - "bid": { - "id": "some-id", - "impid": "test-imp-id", - "price": 1, - "adid": "69595837", - "adm": "{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 3000,\"h\": 2250}},{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 3,\"data\":{\"value\":\"Prebid.org\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}}],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=1/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=1\"],\"jstracker\":\"\"}", - "ext": { - "prebid": { - "type": "native" - } - } - }, - "type": "native" - } - ] - } - ] -} \ No newline at end of file diff --git a/adapters/suntContent/suntContenttest/supplemental/invalid_tag_id.json b/adapters/suntContent/suntContenttest/supplemental/invalid_tag_id.json deleted file mode 100644 index 1493d336f9d..00000000000 --- a/adapters/suntContent/suntContenttest/supplemental/invalid_tag_id.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "expectedMakeRequestsErrors": [ - { - "value": "could not unmarshal openrtb_ext.ImpExtSuntContent: json: cannot unmarshal number into Go struct field ImpExtSuntContent.adUnitID of type string", - "comparison": "literal" - } - ], - "mockBidRequest": { - "id": "test-request-id", - "site": { - "publisher": { - "id": "foo", - "name": "foo" - } - }, - "imp": [ - { - "id": "test-imp-id", - "native": { - "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" - }, - "ext": { - "bidder": { - "adUnitId": 1234 - } - } - } - ] - } -} \ No newline at end of file diff --git a/adapters/suntContent/suntContenttest/supplemental/status_bad_request.json b/adapters/suntContent/suntContenttest/supplemental/status_bad_request.json deleted file mode 100644 index d7e62bb90cb..00000000000 --- a/adapters/suntContent/suntContenttest/supplemental/status_bad_request.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "bidder": { - "adUnitId": "example-tag-id" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://mockup.suntcontent.com/", - "body": { - "cur": [ - "EUR" - ], - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "example-tag-id", - "ext": { - "bidder": { - "adUnitId": "example-tag-id" - } - } - } - ] - } - }, - "mockResponse": { - "status": 400 - } - } - ], - "expectedMakeBidsErrors": [ - { - "value": "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", - "comparison": "literal" - } - ] -} \ No newline at end of file diff --git a/adapters/taboola/params_test.go b/adapters/taboola/params_test.go index 51a9833cdcb..8018c4b9d97 100644 --- a/adapters/taboola/params_test.go +++ b/adapters/taboola/params_test.go @@ -2,8 +2,9 @@ package taboola import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/taboola/taboola.go b/adapters/taboola/taboola.go index 087f661104f..a11fbf6ce73 100644 --- a/adapters/taboola/taboola.go +++ b/adapters/taboola/taboola.go @@ -8,14 +8,15 @@ import ( "strings" "text/template" - "github.com/prebid/openrtb/v19/adcom1" - "github.com/prebid/openrtb/v19/openrtb2" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/adcom1" + "github.com/prebid/openrtb/v20/openrtb2" + + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { @@ -86,7 +87,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R } var response openrtb2.BidResponse - if err := json.Unmarshal(responseData.Body, &response); err != nil { + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { return nil, []error{err} } @@ -131,7 +132,14 @@ func (a *adapter) buildRequest(request *openrtb2.BidRequest) (*adapters.RequestD return nil, fmt.Errorf("unsupported media type for imp: %v", request.Imp[0]) } - url, err := a.buildEndpointURL(request.Site.ID, mediaType) + var taboolaPublisherId string + if request.Site != nil && request.Site.ID != "" { + taboolaPublisherId = request.Site.ID + } else if request.App != nil && request.App.ID != "" { + taboolaPublisherId = request.App.ID + } + + url, err := a.buildEndpointURL(taboolaPublisherId, mediaType) if err != nil { return nil, err } @@ -140,6 +148,7 @@ func (a *adapter) buildRequest(request *openrtb2.BidRequest) (*adapters.RequestD Method: "POST", Uri: url, Body: requestJSON, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), } return requestData, nil @@ -166,11 +175,11 @@ func createTaboolaRequests(request *openrtb2.BidRequest) (taboolaRequests []*ope imp := modifiedRequest.Imp[i] var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { errs = append(errs, err) continue } - if err := json.Unmarshal(bidderExt.Bidder, &taboolaExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &taboolaExt); err != nil { errs = append(errs, err) continue } @@ -206,22 +215,20 @@ func createTaboolaRequests(request *openrtb2.BidRequest) (taboolaRequests []*ope ID: taboolaExt.PublisherId, } - if modifiedRequest.Site == nil { - newSite := &openrtb2.Site{ - ID: taboolaExt.PublisherId, - Name: taboolaExt.PublisherId, - Domain: evaluateDomain(taboolaExt.PublisherDomain, request), - Publisher: publisher, - } - modifiedRequest.Site = newSite - } else { + if modifiedRequest.Site != nil { modifiedSite := *modifiedRequest.Site - modifiedSite.Publisher = publisher modifiedSite.ID = taboolaExt.PublisherId modifiedSite.Name = taboolaExt.PublisherId modifiedSite.Domain = evaluateDomain(taboolaExt.PublisherDomain, request) + modifiedSite.Publisher = publisher modifiedRequest.Site = &modifiedSite } + if modifiedRequest.App != nil { + modifiedApp := *modifiedRequest.App + modifiedApp.ID = taboolaExt.PublisherId + modifiedApp.Publisher = publisher + modifiedRequest.App = &modifiedApp + } if taboolaExt.BCat != nil { modifiedRequest.BCat = taboolaExt.BCat diff --git a/adapters/taboola/taboola_test.go b/adapters/taboola/taboola_test.go index 320d08da22f..082e480943e 100644 --- a/adapters/taboola/taboola_test.go +++ b/adapters/taboola/taboola_test.go @@ -1,14 +1,12 @@ package taboola import ( - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/stretchr/testify/assert" "testing" -) -import ( - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/taboola/taboolatest/exemplary/banner.json b/adapters/taboola/taboolatest/exemplary/banner.json index e550c66e5b4..2656e06116a 100644 --- a/adapters/taboola/taboolatest/exemplary/banner.json +++ b/adapters/taboola/taboolatest/exemplary/banner.json @@ -90,7 +90,8 @@ "h": 300, "w": 300 } - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/taboola/taboolatest/supplemental/emptySiteInRequest.json b/adapters/taboola/taboolatest/exemplary/bannerAppRequest.json similarity index 92% rename from adapters/taboola/taboolatest/supplemental/emptySiteInRequest.json rename to adapters/taboola/taboolatest/exemplary/bannerAppRequest.json index 6e01c457aea..34322399a00 100644 --- a/adapters/taboola/taboolatest/supplemental/emptySiteInRequest.json +++ b/adapters/taboola/taboolatest/exemplary/bannerAppRequest.json @@ -23,13 +23,14 @@ "ext": { "bidder": { "publisherId": "publisher-id", - "tagid": "tag-id" + "tagid": "tag-id", + "tagId": "tag-Id" } } } ], "app": { - "domain": "http://domain.com" + "bundle": "com.app.my" }, "device": { "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36", @@ -62,21 +63,19 @@ } ] }, - "tagid" : "tag-id", + "tagid" : "tag-Id", "ext": { "bidder": { "publisherId": "publisher-id", - "tagid": "tag-id" + "tagid": "tag-id", + "tagId": "tag-Id" } } } ], "app": { - "domain": "http://domain.com" - }, - "site": { "id": "publisher-id", - "name": "publisher-id", + "bundle": "com.app.my", "publisher": { "id": "publisher-id" } @@ -86,7 +85,8 @@ "h": 300, "w": 300 } - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/taboola/taboolatest/exemplary/bannerResolveMacro.json b/adapters/taboola/taboolatest/exemplary/bannerResolveMacro.json index 830af06d202..b673096c0b5 100644 --- a/adapters/taboola/taboolatest/exemplary/bannerResolveMacro.json +++ b/adapters/taboola/taboolatest/exemplary/bannerResolveMacro.json @@ -90,7 +90,8 @@ "h": 300, "w": 300 } - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/taboola/taboolatest/exemplary/multiFormatImpressionsRequest.json b/adapters/taboola/taboolatest/exemplary/multiFormatImpressionsRequest.json index 9d603b8aa66..53cb7672cd7 100644 --- a/adapters/taboola/taboolatest/exemplary/multiFormatImpressionsRequest.json +++ b/adapters/taboola/taboolatest/exemplary/multiFormatImpressionsRequest.json @@ -114,7 +114,8 @@ "h": 300, "w": 300 } - } + }, + "impIDs":["native-impression-id"] }, "mockResponse": { "status": 200, @@ -222,7 +223,8 @@ "h": 300, "w": 300 } - } + }, + "impIDs":["banner-impression-id","banner-impression-id-2"] }, "mockResponse": { "status": 200, diff --git a/adapters/taboola/taboolatest/exemplary/native.json b/adapters/taboola/taboolatest/exemplary/native.json index 88863fdfd64..6a892d3fb59 100644 --- a/adapters/taboola/taboolatest/exemplary/native.json +++ b/adapters/taboola/taboolatest/exemplary/native.json @@ -64,7 +64,8 @@ "h": 300, "w": 300 } - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/taboola/taboolatest/exemplary/nativeResolveMacro.json b/adapters/taboola/taboolatest/exemplary/nativeResolveMacro.json index 03f05db544c..e30f123cc16 100644 --- a/adapters/taboola/taboolatest/exemplary/nativeResolveMacro.json +++ b/adapters/taboola/taboolatest/exemplary/nativeResolveMacro.json @@ -64,7 +64,8 @@ "h": 300, "w": 300 } - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/taboola/taboolatest/exemplary/withPageType.json b/adapters/taboola/taboolatest/exemplary/withPageType.json index 0f0d6435d7e..b3ea5f6357c 100644 --- a/adapters/taboola/taboolatest/exemplary/withPageType.json +++ b/adapters/taboola/taboolatest/exemplary/withPageType.json @@ -93,7 +93,8 @@ "ext": { "pageType": "homepage" } - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/taboola/taboolatest/exemplary/withPosition.json b/adapters/taboola/taboolatest/exemplary/withPosition.json index ea1fdbdd36d..d4d77d39af5 100644 --- a/adapters/taboola/taboolatest/exemplary/withPosition.json +++ b/adapters/taboola/taboolatest/exemplary/withPosition.json @@ -91,7 +91,8 @@ "h": 300, "w": 300 } - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/taboola/taboolatest/supplemental/bidParamsOverrideRequestFields.json b/adapters/taboola/taboolatest/supplemental/bidParamsOverrideRequestFields.json index c4a4279e08f..4138e6d0304 100644 --- a/adapters/taboola/taboolatest/supplemental/bidParamsOverrideRequestFields.json +++ b/adapters/taboola/taboolatest/supplemental/bidParamsOverrideRequestFields.json @@ -92,7 +92,8 @@ "h": 300, "w": 300 } - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/taboola/taboolatest/supplemental/bidderServerError.json b/adapters/taboola/taboolatest/supplemental/bidderServerError.json index 949ceb9338a..829dd466ed8 100644 --- a/adapters/taboola/taboolatest/supplemental/bidderServerError.json +++ b/adapters/taboola/taboolatest/supplemental/bidderServerError.json @@ -88,7 +88,8 @@ "h": 300, "w": 300 } - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 500, diff --git a/adapters/taboola/taboolatest/supplemental/emptyReponseFromBidder.json b/adapters/taboola/taboolatest/supplemental/emptyReponseFromBidder.json index b7d2f2bea44..4533bbf9d81 100644 --- a/adapters/taboola/taboolatest/supplemental/emptyReponseFromBidder.json +++ b/adapters/taboola/taboolatest/supplemental/emptyReponseFromBidder.json @@ -88,7 +88,8 @@ "h": 300, "w": 300 } - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/taboola/taboolatest/supplemental/incorrectResponseImpMapping.json b/adapters/taboola/taboolatest/supplemental/incorrectResponseImpMapping.json index 3cc915cec6f..c70fd287766 100644 --- a/adapters/taboola/taboolatest/supplemental/incorrectResponseImpMapping.json +++ b/adapters/taboola/taboolatest/supplemental/incorrectResponseImpMapping.json @@ -88,7 +88,8 @@ "h": 300, "w": 300 } - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/taboola/taboolatest/supplemental/multiImpressionsRequest.json b/adapters/taboola/taboolatest/supplemental/multiImpressionsRequest.json index 24998b4b948..7b428c45b11 100644 --- a/adapters/taboola/taboolatest/supplemental/multiImpressionsRequest.json +++ b/adapters/taboola/taboolatest/supplemental/multiImpressionsRequest.json @@ -107,7 +107,8 @@ "h": 300, "w": 300 } - } + }, + "impIDs":["impression-id-1","impression-id-2"] }, "mockResponse": { "status": 200, diff --git a/adapters/taboola/taboolatest/supplemental/noValidImpression.json b/adapters/taboola/taboolatest/supplemental/noValidImpression.json index 2ecf5594817..eb3ff2ec384 100644 --- a/adapters/taboola/taboolatest/supplemental/noValidImpression.json +++ b/adapters/taboola/taboolatest/supplemental/noValidImpression.json @@ -40,7 +40,7 @@ }, "expectedMakeRequestsErrors": [ { - "value": "json: cannot unmarshal number into Go struct field ImpExtTaboola.publisherId of type string", + "value": "cannot unmarshal openrtb_ext.ImpExtTaboola.PublisherId: expects \" or n, but found 1", "comparison": "literal" } ] diff --git a/adapters/taboola/taboolatest/supplemental/optionalParamsProvided.json b/adapters/taboola/taboolatest/supplemental/optionalParamsProvided.json index cdf39eeefd4..86b9d9800b8 100644 --- a/adapters/taboola/taboolatest/supplemental/optionalParamsProvided.json +++ b/adapters/taboola/taboolatest/supplemental/optionalParamsProvided.json @@ -99,7 +99,8 @@ }, "bcat": ["excluded-category"], "badv": ["excluded-advertiser"] - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/taboola/taboolatest/supplemental/unexpectedStatusCode.json b/adapters/taboola/taboolatest/supplemental/unexpectedStatusCode.json index d8069578447..551b9a37247 100644 --- a/adapters/taboola/taboolatest/supplemental/unexpectedStatusCode.json +++ b/adapters/taboola/taboolatest/supplemental/unexpectedStatusCode.json @@ -88,7 +88,8 @@ "h": 300, "w": 300 } - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 302, diff --git a/adapters/tappx/params_test.go b/adapters/tappx/params_test.go index 8a248345994..07b56bf80c8 100644 --- a/adapters/tappx/params_test.go +++ b/adapters/tappx/params_test.go @@ -2,8 +2,9 @@ package tappx import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/tappx/tappx.go b/adapters/tappx/tappx.go index d66a10a2bbe..a0c6d9c7dd2 100644 --- a/adapters/tappx/tappx.go +++ b/adapters/tappx/tappx.go @@ -10,12 +10,13 @@ import ( "text/template" "time" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) const TAPPX_BIDDER_VERSION = "1.5" @@ -57,13 +58,13 @@ func (a *TappxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapt } var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(request.Imp[0].Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(request.Imp[0].Ext, &bidderExt); err != nil { return nil, []error{&errortypes.BadInput{ Message: "Error parsing bidderExt object", }} } var tappxExt openrtb_ext.ExtImpTappx - if err := json.Unmarshal(bidderExt.Bidder, &tappxExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &tappxExt); err != nil { return nil, []error{&errortypes.BadInput{ Message: "Error parsing tappxExt parameters", }} @@ -86,8 +87,7 @@ func (a *TappxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapt }} } - var test int - test = int(request.Test) + test := int(request.Test) url, err := a.buildEndpointURL(&tappxExt, test) if url == "" { @@ -113,6 +113,7 @@ func (a *TappxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapt Uri: url, Body: reqJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }}, []error{} } @@ -131,8 +132,13 @@ func (a *TappxAdapter) buildEndpointURL(params *openrtb_ext.ExtImpTappx, test in } } - tappxHost := "tappx.com" isNewEndpoint, err := regexp.Match(`^(zz|vz)[0-9]{3,}([a-z]{2,3}|test)$`, []byte(params.Endpoint)) + if err != nil { + return "", &errortypes.BadInput{ + Message: "Unable to match params.Endpoint " + string(params.Endpoint) + "): " + err.Error(), + } + } + var tappxHost string if isNewEndpoint { tappxHost = params.Endpoint + ".pub.tappx.com/rtb/" } else { @@ -193,7 +199,7 @@ func (a *TappxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRe } var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } diff --git a/adapters/tappx/tappx_test.go b/adapters/tappx/tappx_test.go index c1b711426fb..bf4e4f9f17e 100644 --- a/adapters/tappx/tappx_test.go +++ b/adapters/tappx/tappx_test.go @@ -4,10 +4,11 @@ import ( "regexp" "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestJsonSamples(t *testing.T) { @@ -38,13 +39,13 @@ func TestTsValue(t *testing.T) { bidderTappx := bidder.(*TappxAdapter) - var test int - test = 0 + test := 0 var tappxExt openrtb_ext.ExtImpTappx tappxExt.Endpoint = "DUMMYENDPOINT" tappxExt.TappxKey = "dummy-tappx-key" url, err := bidderTappx.buildEndpointURL(&tappxExt, test) + require.NoError(t, err, "buildEndpointURL") match, err := regexp.MatchString(`http://ssp\.api\.tappx\.com/rtb/v2/DUMMYENDPOINT\?tappxkey=dummy-tappx-key&ts=[0-9]{13}&type_cnn=prebid&v=1\.5`, url) if err != nil { diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression-extra.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression-extra.json index ef8e5e394a9..fc03a4daa25 100644 --- a/adapters/tappx/tappxtest/exemplary/single-banner-impression-extra.json +++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression-extra.json @@ -75,7 +75,8 @@ "bcrid": ["4","5","6"] } } - } + }, + "impIDs":["adunit-1"] }, "mockResponse": { "status": 200, diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json index 5af06ad55a8..478e4ace29a 100644 --- a/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json +++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json @@ -66,7 +66,8 @@ "tappxkey": "pub-12345-android-9876" } } - } + }, + "impIDs":["adunit-1"] }, "mockResponse": { "status": 200, diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json index dbd342b3c1e..7028b7a4f41 100644 --- a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json +++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json @@ -66,7 +66,8 @@ "tappxkey": "pub-12345-android-9876" } } - } + }, + "impIDs":["adunit-1"] }, "mockResponse": { "status": 200, diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-site.json b/adapters/tappx/tappxtest/exemplary/single-banner-site.json index dde889dd5bc..ea9057f108b 100644 --- a/adapters/tappx/tappxtest/exemplary/single-banner-site.json +++ b/adapters/tappx/tappxtest/exemplary/single-banner-site.json @@ -74,7 +74,8 @@ "tappxkey": "pub-12345-site-9876" } } - } + }, + "impIDs":["adunit-1"] }, "mockResponse": { "status": 200, diff --git a/adapters/tappx/tappxtest/exemplary/single-video-impression.json b/adapters/tappx/tappxtest/exemplary/single-video-impression.json index a9507df56a7..a63bc17ee78 100644 --- a/adapters/tappx/tappxtest/exemplary/single-video-impression.json +++ b/adapters/tappx/tappxtest/exemplary/single-video-impression.json @@ -71,7 +71,8 @@ "tappxkey": "pub-12345-android-9876" } } - } + }, + "impIDs":["video-adunit-2"] }, "mockResponse": { "status": 200, diff --git a/adapters/tappx/tappxtest/exemplary/single-video-site.json b/adapters/tappx/tappxtest/exemplary/single-video-site.json index 5e2ba58b03e..60a8fdd9a9b 100644 --- a/adapters/tappx/tappxtest/exemplary/single-video-site.json +++ b/adapters/tappx/tappxtest/exemplary/single-video-site.json @@ -79,7 +79,8 @@ "tappxkey": "pub-12345-site-9876" } } - } + }, + "impIDs":["video-adunit-2"] }, "mockResponse": { "status": 200, diff --git a/adapters/tappx/tappxtest/supplemental/204status.json b/adapters/tappx/tappxtest/supplemental/204status.json index 8e3bb4b5ed7..a6b2a49b68a 100644 --- a/adapters/tappx/tappxtest/supplemental/204status.json +++ b/adapters/tappx/tappxtest/supplemental/204status.json @@ -60,7 +60,8 @@ "tappxkey": "pub-12345-android-9876" } } - } + }, + "impIDs":["adunit-1"] }, "mockResponse": { "status": 204, diff --git a/adapters/tappx/tappxtest/supplemental/bidfloor.json b/adapters/tappx/tappxtest/supplemental/bidfloor.json index 7c2c53063fd..44a1712d3c0 100644 --- a/adapters/tappx/tappxtest/supplemental/bidfloor.json +++ b/adapters/tappx/tappxtest/supplemental/bidfloor.json @@ -69,7 +69,8 @@ "tappxkey": "pub-12345-android-9876" } } - } + }, + "impIDs":["adunit-1"] }, "mockResponse": { "status": 200, diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status.json b/adapters/tappx/tappxtest/supplemental/http-err-status.json index 22d0afe3180..e8b1a2e532e 100644 --- a/adapters/tappx/tappxtest/supplemental/http-err-status.json +++ b/adapters/tappx/tappxtest/supplemental/http-err-status.json @@ -60,7 +60,8 @@ "tappxkey": "pub-12345-android-9876" } } - } + }, + "impIDs":["adunit-1"] }, "mockResponse": { "status": 400 diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status2.json b/adapters/tappx/tappxtest/supplemental/http-err-status2.json index 011f80a5ff8..3d68fdaf7ff 100644 --- a/adapters/tappx/tappxtest/supplemental/http-err-status2.json +++ b/adapters/tappx/tappxtest/supplemental/http-err-status2.json @@ -60,7 +60,8 @@ "tappxkey": "pub-12345-android-9876" } } - } + }, + "impIDs":["adunit-1"] }, "mockResponse": { "status": 500 diff --git a/adapters/teads/teads.go b/adapters/teads/teads.go index 9c2bb57fc57..402392d1ccc 100644 --- a/adapters/teads/teads.go +++ b/adapters/teads/teads.go @@ -8,12 +8,13 @@ import ( "strconv" "text/template" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) // Builder builds a new instance of the Teads adapter for the given bidder with the given config. @@ -59,6 +60,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E Uri: endpointURL, Body: reqJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }}, []error{} } @@ -76,7 +78,7 @@ func updateImpObject(imps []openrtb2.Imp) error { } var defaultImpExt defaultBidderImpExtension - if err := json.Unmarshal(imp.Ext, &defaultImpExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &defaultImpExt); err != nil { return &errortypes.BadInput{ Message: "Error parsing Imp.Ext object", } @@ -135,7 +137,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, _ *adapters.Req } var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -171,7 +173,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, _ *adapters.Req func getTeadsRendererFromBidExt(ext json.RawMessage) (*teadsBidExt, []error) { var bidExtTeads teadsBidExt - if err := json.Unmarshal(ext, &bidExtTeads); err != nil { + if err := jsonutil.Unmarshal(ext, &bidExtTeads); err != nil { return nil, []error{err} } if bidExtTeads.Prebid.Meta.RendererName == "" { diff --git a/adapters/teads/teads_test.go b/adapters/teads/teads_test.go index 791ce7aed6f..8499e6f1399 100644 --- a/adapters/teads/teads_test.go +++ b/adapters/teads/teads_test.go @@ -1,16 +1,17 @@ package teads import ( - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/stretchr/testify/assert" "testing" + + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderTeads, config.Adapter{ - Endpoint: "https://a.teads.tv/prebid-server/bid-request"}, config.Server{ExternalUrl: "https://a.teads.tv/prebid-server/bid-request", GvlID: 1, DataCenter: "2"}) + Endpoint: "https://psrv.teads.tv/prebid-server/bid-request"}, config.Server{ExternalUrl: "https://psrv.teads.tv/prebid-server/bid-request", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -21,7 +22,7 @@ func TestJsonSamples(t *testing.T) { func TestEndpointTemplateMalformed(t *testing.T) { _, buildErr := Builder(openrtb_ext.BidderTeads, config.Adapter{ - Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "https://a.teads.tv/prebid-server/bid-request", GvlID: 1, DataCenter: "2"}) + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "https://psrv.teads.tv/prebid-server/bid-request", GvlID: 1, DataCenter: "2"}) assert.Error(t, buildErr) } diff --git a/adapters/teads/teadstest/exemplary/simple-banner-with-format.json b/adapters/teads/teadstest/exemplary/simple-banner-with-format.json index b2677e6faba..418e264c19b 100644 --- a/adapters/teads/teadstest/exemplary/simple-banner-with-format.json +++ b/adapters/teads/teadstest/exemplary/simple-banner-with-format.json @@ -39,7 +39,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ @@ -79,7 +79,8 @@ } } } - } + }, + "impIDs":["b6321d41-3840-4cb3-baad-b6fc5b0c8553"] }, "mockResponse": { "status": 200, diff --git a/adapters/teads/teadstest/exemplary/simple-banner.json b/adapters/teads/teadstest/exemplary/simple-banner.json index 2d9be7c7368..cb88466d3a0 100644 --- a/adapters/teads/teadstest/exemplary/simple-banner.json +++ b/adapters/teads/teadstest/exemplary/simple-banner.json @@ -35,7 +35,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ @@ -69,7 +69,8 @@ } } } - } + }, + "impIDs":["b6321d41-3840-4cb3-baad-b6fc5b0c8553"] }, "mockResponse": { "status": 200, diff --git a/adapters/teads/teadstest/exemplary/simple-video.json b/adapters/teads/teadstest/exemplary/simple-video.json index 814569a47e1..e08f90daa8c 100644 --- a/adapters/teads/teadstest/exemplary/simple-video.json +++ b/adapters/teads/teadstest/exemplary/simple-video.json @@ -48,7 +48,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ @@ -95,7 +95,8 @@ } } } - } + }, + "impIDs":["b6321d41-3840-4cb3-baad-b6fc5b0c8553"] }, "mockResponse": { "status": 200, diff --git a/adapters/teads/teadstest/supplemental/bid-id-does-not-match.json b/adapters/teads/teadstest/supplemental/bid-id-does-not-match.json index 384e72fb537..abfaacd979b 100644 --- a/adapters/teads/teadstest/supplemental/bid-id-does-not-match.json +++ b/adapters/teads/teadstest/supplemental/bid-id-does-not-match.json @@ -48,7 +48,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ @@ -95,7 +95,8 @@ } } } - } + }, + "impIDs":["b6321d41-3840-4cb3-baad-b6fc5b0c8553"] }, "mockResponse": { "status": 200, diff --git a/adapters/teads/teadstest/supplemental/currency-empty-string.json b/adapters/teads/teadstest/supplemental/currency-empty-string.json index 5f0d700b14b..724fa23dea8 100644 --- a/adapters/teads/teadstest/supplemental/currency-empty-string.json +++ b/adapters/teads/teadstest/supplemental/currency-empty-string.json @@ -48,7 +48,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ @@ -95,7 +95,8 @@ } } } - } + }, + "impIDs":["b6321d41-3840-4cb3-baad-b6fc5b0c8553"] }, "mockResponse": { "status": 200, diff --git a/adapters/teads/teadstest/supplemental/no-impression-response.json b/adapters/teads/teadstest/supplemental/no-impression-response.json index 814569a47e1..e08f90daa8c 100644 --- a/adapters/teads/teadstest/supplemental/no-impression-response.json +++ b/adapters/teads/teadstest/supplemental/no-impression-response.json @@ -48,7 +48,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ @@ -95,7 +95,8 @@ } } } - } + }, + "impIDs":["b6321d41-3840-4cb3-baad-b6fc5b0c8553"] }, "mockResponse": { "status": 200, diff --git a/adapters/teads/teadstest/supplemental/renderer-name-empty.json b/adapters/teads/teadstest/supplemental/renderer-name-empty.json index da4ee9a5094..00744f792d9 100644 --- a/adapters/teads/teadstest/supplemental/renderer-name-empty.json +++ b/adapters/teads/teadstest/supplemental/renderer-name-empty.json @@ -44,7 +44,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ @@ -87,7 +87,8 @@ } } } - } + }, + "impIDs":["b6321d41-3840-4cb3-baad-b6fc5b0c8553"] }, "mockResponse": { "status": 200, diff --git a/adapters/teads/teadstest/supplemental/renderer-version-empty.json b/adapters/teads/teadstest/supplemental/renderer-version-empty.json index e9e7b278dcb..7960ea569ed 100644 --- a/adapters/teads/teadstest/supplemental/renderer-version-empty.json +++ b/adapters/teads/teadstest/supplemental/renderer-version-empty.json @@ -44,7 +44,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ @@ -87,7 +87,8 @@ } } } - } + }, + "impIDs":["b6321d41-3840-4cb3-baad-b6fc5b0c8553"] }, "mockResponse": { "status": 200, diff --git a/adapters/teads/teadstest/supplemental/status-400.json b/adapters/teads/teadstest/supplemental/status-400.json index cd9fafff0a1..32953be4f85 100644 --- a/adapters/teads/teadstest/supplemental/status-400.json +++ b/adapters/teads/teadstest/supplemental/status-400.json @@ -24,7 +24,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ @@ -48,7 +48,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/teads/teadstest/supplemental/status-500.json b/adapters/teads/teadstest/supplemental/status-500.json index 337d4754006..c07facc1c43 100644 --- a/adapters/teads/teadstest/supplemental/status-500.json +++ b/adapters/teads/teadstest/supplemental/status-500.json @@ -24,7 +24,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://a.teads.tv/prebid-server/bid-request", + "uri": "https://psrv.teads.tv/prebid-server/bid-request", "body": { "id": "test-request-id", "imp": [ @@ -48,7 +48,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 500, diff --git a/adapters/telaria/params_test.go b/adapters/telaria/params_test.go index efa3fba1be9..7b1dfe08546 100644 --- a/adapters/telaria/params_test.go +++ b/adapters/telaria/params_test.go @@ -2,8 +2,9 @@ package telaria import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/telaria/telaria.go b/adapters/telaria/telaria.go index bbe600178f4..1805bfaea0a 100644 --- a/adapters/telaria/telaria.go +++ b/adapters/telaria/telaria.go @@ -6,11 +6,12 @@ import ( "net/http" "strconv" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) const Endpoint = "https://ads.tremorhub.com/ad/rtb/prebid" @@ -99,7 +100,7 @@ func GetHeaders(request *openrtb2.BidRequest) *http.Header { // Checks the imp[i].ext object and returns a imp.ext object as per ExtImpTelaria format func (a *TelariaAdapter) FetchTelariaExtImpParams(imp *openrtb2.Imp) (*openrtb_ext.ExtImpTelaria, error) { var bidderExt adapters.ExtImpBidder - err := json.Unmarshal(imp.Ext, &bidderExt) + err := jsonutil.Unmarshal(imp.Ext, &bidderExt) if err != nil { err = &errortypes.BadInput{ @@ -110,7 +111,7 @@ func (a *TelariaAdapter) FetchTelariaExtImpParams(imp *openrtb2.Imp) (*openrtb_e } var telariaExt openrtb_ext.ExtImpTelaria - err = json.Unmarshal(bidderExt.Bidder, &telariaExt) + err = jsonutil.Unmarshal(bidderExt.Bidder, &telariaExt) if err != nil { return nil, err @@ -200,11 +201,14 @@ func (a *TelariaAdapter) MakeRequests(requestIn *openrtb2.BidRequest, reqInfo *a return nil, []error{err} } + if telariaImpExt == nil { + return nil, []error{&errortypes.BadInput{Message: "Telaria: nil ExtImpTelaria object"}} + } // Swap the tagID with adCode imp.TagID = telariaImpExt.AdCode // Add the Extra from Imp to the top level Ext - if telariaImpExt != nil && telariaImpExt.Extra != nil { + if telariaImpExt.Extra != nil { request.Ext, err = json.Marshal(&telariaBidExt{Extra: telariaImpExt.Extra}) if err != nil { return nil, []error{err} @@ -228,6 +232,7 @@ func (a *TelariaAdapter) MakeRequests(requestIn *openrtb2.BidRequest, reqInfo *a Uri: a.FetchEndpoint(), Body: reqJSON, Headers: *GetHeaders(&request), + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }}, nil } @@ -267,7 +272,7 @@ func (a *TelariaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, external responseBody := response.Body var bidResp openrtb2.BidResponse - if err := json.Unmarshal(responseBody, &bidResp); err != nil { + if err := jsonutil.Unmarshal(responseBody, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: "Telaria: Bad Server Response", }} diff --git a/adapters/telaria/telaria_test.go b/adapters/telaria/telaria_test.go index f8008835ac3..8c55e7b900c 100644 --- a/adapters/telaria/telaria_test.go +++ b/adapters/telaria/telaria_test.go @@ -3,9 +3,9 @@ package telaria import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/telaria/telariatest/exemplary/multiple-video-web.json b/adapters/telaria/telariatest/exemplary/multiple-video-web.json index e28d0cfa8d3..dd60aafdbf1 100644 --- a/adapters/telaria/telariatest/exemplary/multiple-video-web.json +++ b/adapters/telaria/telariatest/exemplary/multiple-video-web.json @@ -141,7 +141,8 @@ "custom": "1234" } } - } + }, + "impIDs":["0_0"] }, "mockResponse": { "status": 200, diff --git a/adapters/telaria/telariatest/exemplary/multiple-vidoe-app.json b/adapters/telaria/telariatest/exemplary/multiple-vidoe-app.json index 49bdb6b2b21..d1cf513846b 100644 --- a/adapters/telaria/telariatest/exemplary/multiple-vidoe-app.json +++ b/adapters/telaria/telariatest/exemplary/multiple-vidoe-app.json @@ -153,7 +153,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["0_0"] }, "mockResponse": { "status": 200, diff --git a/adapters/telaria/telariatest/exemplary/video-app.json b/adapters/telaria/telariatest/exemplary/video-app.json index 4041b01d806..75e424ae7d0 100644 --- a/adapters/telaria/telariatest/exemplary/video-app.json +++ b/adapters/telaria/telariatest/exemplary/video-app.json @@ -123,7 +123,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/telaria/telariatest/exemplary/video-web.json b/adapters/telaria/telariatest/exemplary/video-web.json index 1ce1d3ad669..d7df8f11148 100644 --- a/adapters/telaria/telariatest/exemplary/video-web.json +++ b/adapters/telaria/telariatest/exemplary/video-web.json @@ -111,7 +111,8 @@ "custom": "1234" } } - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/telaria/telariatest/supplemental/invalid-response.json b/adapters/telaria/telariatest/supplemental/invalid-response.json index 3f614a0724c..90ca9809939 100644 --- a/adapters/telaria/telariatest/supplemental/invalid-response.json +++ b/adapters/telaria/telariatest/supplemental/invalid-response.json @@ -88,7 +88,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/telaria/telariatest/supplemental/status-code-bad-request.json b/adapters/telaria/telariatest/supplemental/status-code-bad-request.json index 0b5d8a85982..0faec2b22ec 100644 --- a/adapters/telaria/telariatest/supplemental/status-code-bad-request.json +++ b/adapters/telaria/telariatest/supplemental/status-code-bad-request.json @@ -64,7 +64,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 400 diff --git a/adapters/telaria/telariatest/supplemental/status-code-no-content.json b/adapters/telaria/telariatest/supplemental/status-code-no-content.json index ffb183f4121..40d49c7554b 100644 --- a/adapters/telaria/telariatest/supplemental/status-code-no-content.json +++ b/adapters/telaria/telariatest/supplemental/status-code-no-content.json @@ -67,7 +67,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/telaria/telariatest/supplemental/status-code-other-error.json b/adapters/telaria/telariatest/supplemental/status-code-other-error.json index 15e4b7f87d8..f93683ffd1c 100644 --- a/adapters/telaria/telariatest/supplemental/status-code-other-error.json +++ b/adapters/telaria/telariatest/supplemental/status-code-other-error.json @@ -67,7 +67,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 306 diff --git a/adapters/telaria/telariatest/supplemental/status-code-service-unavailable.json b/adapters/telaria/telariatest/supplemental/status-code-service-unavailable.json index b92d4ea8ba1..335b88f0d4c 100644 --- a/adapters/telaria/telariatest/supplemental/status-code-service-unavailable.json +++ b/adapters/telaria/telariatest/supplemental/status-code-service-unavailable.json @@ -67,7 +67,8 @@ "buyeruid": "awesome-user" }, "tmax": 1000 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 503 diff --git a/adapters/theadx/params_test.go b/adapters/theadx/params_test.go new file mode 100644 index 00000000000..09440b335c6 --- /dev/null +++ b/adapters/theadx/params_test.go @@ -0,0 +1,66 @@ +package theadx + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/theadx.json +// +// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.theadx + +// TestValidParams makes sure that the theadx schema accepts all imp.ext fields which we intend to support. +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderTheadx, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected theadx params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the theadx schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderTheadx, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"tagid":321}`, + `{"tagid":321,"wid":"456"}`, + `{"tagid":321,"pid":"12345"}`, + `{"tagid":321,"pname":"plc_mobile_300x250"}`, + `{"tagid":321,"inv":321,"mname":"pcl1"}`, + `{"tagid":"123","wid":"456","pid":"12345","pname":"plc_mobile_300x250"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"notmid":"123"}`, + `{"mid":"placementID"}`, + `{"inv":321,"mname":12345}`, + `{"inv":321}`, + `{"inv":"321"}`, + `{"mname":"12345"}`, + `{"mid":"123","priceType":"GROSS"}`, +} diff --git a/adapters/theadx/theadx.go b/adapters/theadx/theadx.go new file mode 100644 index 00000000000..34b1e1a3b48 --- /dev/null +++ b/adapters/theadx/theadx.go @@ -0,0 +1,150 @@ +package theadx + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +type adapter struct { + endpoint string +} + +// Builder builds a new instance of the theadx adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} +func getHeaders(request *openrtb2.BidRequest) http.Header { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("X-Openrtb-Version", "2.5") + headers.Add("X-TEST", "1") + + if request.Device != nil { + if len(request.Device.UA) > 0 { + headers.Add("X-Device-User-Agent", request.Device.UA) + } + + if len(request.Device.IPv6) > 0 { + headers.Add("X-Real-IP", request.Device.IPv6) + } + + if len(request.Device.IP) > 0 { + headers.Add("X-Real-IP", request.Device.IP) + } + } + + return headers +} +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errors []error + var validImps = make([]openrtb2.Imp, 0, len(request.Imp)) + + for _, imp := range request.Imp { + var bidderExt adapters.ExtImpBidder + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { + errors = append(errors, &errortypes.BadInput{ + Message: err.Error(), + }) + continue + } + + var theadxImpExt openrtb_ext.ExtImpTheadx + if err := jsonutil.Unmarshal(bidderExt.Bidder, &theadxImpExt); err != nil { + errors = append(errors, &errortypes.BadInput{ + Message: err.Error(), + }) + continue + } + + imp.TagID = theadxImpExt.TagID.String() + validImps = append(validImps, imp) + } + + request.Imp = validImps + + requestJSON, err := json.Marshal(request) + if err != nil { + errors = append(errors, err) + return nil, errors + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: requestJSON, + Headers: getHeaders(request), + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), + } + + return []*adapters.RequestData{requestData}, errors +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if responseData.StatusCode == http.StatusNoContent { + return nil, nil + } + + if responseData.StatusCode == http.StatusBadRequest { + err := &errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", responseData.StatusCode), + } + return nil, []error{err} + } + + if responseData.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d.", responseData.StatusCode), + } + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + var errors []error + for _, seatBid := range response.SeatBid { + for i, bid := range seatBid.Bid { + bidType, err := getMediaTypeForBid(bid) + if err != nil { + errors = append(errors, err) + continue + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + }) + } + } + + return bidResponse, errors +} + +func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + if bid.Ext != nil { + var bidExt openrtb_ext.ExtBid + err := jsonutil.Unmarshal(bid.Ext, &bidExt) + if err == nil && bidExt.Prebid != nil { + return openrtb_ext.ParseBidType(string(bidExt.Prebid.Type)) + } + } + + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Failed to parse impression \"%s\" mediatype", bid.ImpID), + } +} diff --git a/adapters/theadx/theadx_test.go b/adapters/theadx/theadx_test.go new file mode 100644 index 00000000000..a945bd15fee --- /dev/null +++ b/adapters/theadx/theadx_test.go @@ -0,0 +1,20 @@ +package theadx + +import ( + "testing" + + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderTheadx, config.Adapter{ + Endpoint: "https://ssp.theadx.com/request"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "theadxtest", bidder) +} diff --git a/adapters/theadx/theadxtest/exemplary/dynamic-tag.json b/adapters/theadx/theadxtest/exemplary/dynamic-tag.json new file mode 100644 index 00000000000..8dc586b6a15 --- /dev/null +++ b/adapters/theadx/theadxtest/exemplary/dynamic-tag.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "tagid": "123", + "pname": "placement" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }, { + "id": "test-imp-id2", + "ext": { + "bidder": { + "tagid": "123", + "wid": 456, + "pname": "placement1" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 300 + }] + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://ssp.theadx.com/request", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "tagid": "123", + "ext": { + "bidder": { + "tagid": "123", + "pname": "placement" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }, { + "id": "test-imp-id2", + "tagid": "123", + "ext": { + "bidder": { + "tagid": "123", + "wid": 456, + "pname": "placement1" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 300 + }] + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "impIDs":["test-imp-id", "test-imp-id2"] + }, + "mockResponse": { + "status": 204 + } + }], + "expectedMakeRequestsErrors": [], + "expectedBidResponses": [] +} diff --git a/adapters/theadx/theadxtest/exemplary/multi-format.json b/adapters/theadx/theadxtest/exemplary/multi-format.json new file mode 100644 index 00000000000..0190f92c642 --- /dev/null +++ b/adapters/theadx/theadxtest/exemplary/multi-format.json @@ -0,0 +1,163 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id-1", + "ext": { + "bidder": { + "tagid": "123" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "placement": 1 + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }, { + "id": "test-imp-id-2", + "ext": { + "bidder": { + "tagid": "123" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "placement": 1 + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://ssp.theadx.com/request", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id-1", + "ext": { + "bidder": { + "tagid": "123" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "placement": 1 + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "123" + }, { + "id": "test-imp-id-2", + "ext": { + "bidder": { + "tagid": "123" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "placement": 1 + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "123" + }] + }, + "impIDs":["test-imp-id-1", "test-imp-id-2"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id-1", + "impid": "test-imp-id-1", + "price": 10, + "adm": "{video xml}", + "adomain": [], + "crid": "test-creative-id-1", + "ext": { + "prebid": { + "type": "video" + } + } + }] + }, { + "bid": [{ + "id": "test-bid-id-2", + "impid": "test-imp-id-2", + "price": 2, + "adm": "{banner html}", + "adomain": [ "ad-domain" ], + "crid": "test-creative-id-2", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }], + "cur": "TRY" + } + } + }], + "expectedBidResponses": [{ + "currency": "TRY", + "bids": [{ + "bid": { + "id": "test-bid-id-1", + "impid": "test-imp-id-1", + "price": 10, + "adm": "{video xml}", + "crid": "test-creative-id-1", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + }, { + "bid": { + "id": "test-bid-id-2", + "impid": "test-imp-id-2", + "price": 2, + "adm": "{banner html}", + "adomain": [ "ad-domain" ], + "crid": "test-creative-id-2", + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + }] + }] +} diff --git a/adapters/theadx/theadxtest/exemplary/multi-native.json b/adapters/theadx/theadxtest/exemplary/multi-native.json new file mode 100644 index 00000000000..d58fea7ae5b --- /dev/null +++ b/adapters/theadx/theadxtest/exemplary/multi-native.json @@ -0,0 +1,129 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id-1", + "ext": { + "bidder": { + "tagid": "123" + } + }, + "native": { + "request": "{json string 1}", + "ver": "1.2" + } + }, { + "id": "test-imp-id-2", + "ext": { + "bidder": { + "tagid": "124" + } + }, + "native": { + "request": "{json string 2}", + "ver": "1.2" + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://ssp.theadx.com/request", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id-1", + "ext": { + "bidder": { + "tagid": "123" + } + }, + "native": { + "request": "{json string 1}", + "ver": "1.2" + }, + "tagid": "123" + }, { + "id": "test-imp-id-2", + "ext": { + "bidder": { + "tagid": "124" + } + }, + "native": { + "request": "{json string 2}", + "ver": "1.2" + }, + "tagid": "124" + }] + }, + "impIDs":["test-imp-id-1", "test-imp-id-2"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id-1", + "impid": "test-imp-id-1", + "price": 10, + "adm": "{json response string 1}", + "adomain": [], + "crid": "test-creative-id-1", + "ext": { + "prebid": { + "type": "native" + } + } + }, { + "id": "test-bid-id-2", + "impid": "test-imp-id-2", + "price": 2, + "adm": "{json response string 2}", + "adomain": [ "ad-domain" ], + "crid": "test-creative-id-2", + "ext": { + "prebid": { + "type": "native" + } + } + }] + }], + "cur": "EUR" + } + } + }], + "expectedBidResponses": [{ + "currency": "EUR", + "bids": [{ + "bid": { + "id": "test-bid-id-1", + "impid": "test-imp-id-1", + "price": 10, + "adm": "{json response string 1}", + "crid": "test-creative-id-1", + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + }, { + "bid": { + "id": "test-bid-id-2", + "impid": "test-imp-id-2", + "price": 2, + "adm": "{json response string 2}", + "adomain": [ "ad-domain" ], + "crid": "test-creative-id-2", + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + }] + }] +} diff --git a/adapters/theadx/theadxtest/exemplary/single-banner.json b/adapters/theadx/theadxtest/exemplary/single-banner.json new file mode 100644 index 00000000000..6600e3b3b25 --- /dev/null +++ b/adapters/theadx/theadxtest/exemplary/single-banner.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "tagid": "123" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://ssp.theadx.com/request", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "tagid": "123" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "123" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "adomain": [ "test.com" ], + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "crid": "test-creative-id", + "adomain": [ "test.com" ], + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + }] +} diff --git a/adapters/theadx/theadxtest/exemplary/single-native.json b/adapters/theadx/theadxtest/exemplary/single-native.json new file mode 100644 index 00000000000..b20407bd28b --- /dev/null +++ b/adapters/theadx/theadxtest/exemplary/single-native.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "tagid": "123" + } + }, + "native": { + "request": "{json string}", + "ver": "1.2" + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://ssp.theadx.com/request", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "tagid": "123" + } + }, + "native": { + "request": "{json string}", + "ver": "1.2" + }, + "tagid": "123" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{json response string}", + "adomain": [], + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "native" + } + } + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{json response string}", + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + } + ] + }] +} diff --git a/adapters/theadx/theadxtest/exemplary/single-video.json b/adapters/theadx/theadxtest/exemplary/single-video.json new file mode 100644 index 00000000000..d6b11ae39f1 --- /dev/null +++ b/adapters/theadx/theadxtest/exemplary/single-video.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "tagid": "123" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "placement": 1 + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://ssp.theadx.com/request", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "tagid": "123" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "placement": 1 + }, + "tagid": "123" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{vast xml}", + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "video" + } + } + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{vast xml}", + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + }] +} diff --git a/adapters/theadx/theadxtest/supplemental/bad-request.json b/adapters/theadx/theadxtest/supplemental/bad-request.json new file mode 100644 index 00000000000..241c538063a --- /dev/null +++ b/adapters/theadx/theadxtest/supplemental/bad-request.json @@ -0,0 +1,49 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "tagid": 12345 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ssp.theadx.com/request", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "tagid": 12345 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "12345" + }] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/theadx/theadxtest/supplemental/empty-response.json b/adapters/theadx/theadxtest/supplemental/empty-response.json new file mode 100644 index 00000000000..eed3f72c72d --- /dev/null +++ b/adapters/theadx/theadxtest/supplemental/empty-response.json @@ -0,0 +1,43 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "tagid": 123 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ssp.theadx.com/request", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "tagid": 123 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "123" + }] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/theadx/theadxtest/supplemental/nobid-response.json b/adapters/theadx/theadxtest/supplemental/nobid-response.json new file mode 100644 index 00000000000..ccefe2c9061 --- /dev/null +++ b/adapters/theadx/theadxtest/supplemental/nobid-response.json @@ -0,0 +1,50 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "tagid": 123 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ssp.theadx.com/request", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "tagid": 123 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "123" + }] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 204, + "body": { + "id": "test-request-id", + "seatbid": null, + "bidid": null, + "cur": null + } + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} diff --git a/adapters/theadx/theadxtest/supplemental/server-error.json b/adapters/theadx/theadxtest/supplemental/server-error.json new file mode 100644 index 00000000000..b5c6eb87193 --- /dev/null +++ b/adapters/theadx/theadxtest/supplemental/server-error.json @@ -0,0 +1,50 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "tagid": 123 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ssp.theadx.com/request", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "tagid": 123 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "123" + }] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 500, + "body": "Server error" + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500.", + "comparison": "literal" + } + ] +} diff --git a/adapters/theadx/theadxtest/supplemental/unparsable-response.json b/adapters/theadx/theadxtest/supplemental/unparsable-response.json new file mode 100644 index 00000000000..fa74cfdc989 --- /dev/null +++ b/adapters/theadx/theadxtest/supplemental/unparsable-response.json @@ -0,0 +1,50 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "tagid": 123 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ssp.theadx.com/request", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "tagid": 123 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "123" + }] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "expect { or n, but found \"", + "comparison": "literal" + } + ] +} diff --git a/adapters/thetradedesk/params_test.go b/adapters/thetradedesk/params_test.go new file mode 100644 index 00000000000..05febe45fab --- /dev/null +++ b/adapters/thetradedesk/params_test.go @@ -0,0 +1,53 @@ +package thetradedesk + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderTheTradeDesk, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected TheTradeDesk params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the TheTradeDesk schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderTheTradeDesk, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"publisherId": "123456"}`, + `{"publisherId": "pub-123456"}`, + `{"publisherId": "publisherIDAllString"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"publisherId": 123456}`, + `{"publisherId": 0}`, +} diff --git a/adapters/thetradedesk/thetradedesk.go b/adapters/thetradedesk/thetradedesk.go new file mode 100644 index 00000000000..0288b4bb097 --- /dev/null +++ b/adapters/thetradedesk/thetradedesk.go @@ -0,0 +1,197 @@ +package thetradedesk + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "regexp" + "text/template" + + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" + + "github.com/prebid/openrtb/v20/openrtb2" +) + +const PREBID_INTEGRATION_TYPE = "1" + +type adapter struct { + bidderEndpoint string +} + +type ExtImpBidderTheTradeDesk struct { + adapters.ExtImpBidder +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + pubID, err := getPublisherId(request.Imp) + + if err != nil { + return nil, []error{err} + } + + modifiedImps := make([]openrtb2.Imp, 0, len(request.Imp)) + + for _, imp := range request.Imp { + + if imp.Banner != nil { + if len(imp.Banner.Format) > 0 { + firstFormat := imp.Banner.Format[0] + bannerCopy := *imp.Banner + bannerCopy.H = &firstFormat.H + bannerCopy.W = &firstFormat.W + imp.Banner = &bannerCopy + + } + } + + modifiedImps = append(modifiedImps, imp) + } + + request.Imp = modifiedImps + + if request.Site != nil { + siteCopy := *request.Site + if siteCopy.Publisher != nil { + publisherCopy := *siteCopy.Publisher + if pubID != "" { + publisherCopy.ID = pubID + } + siteCopy.Publisher = &publisherCopy + } else { + siteCopy.Publisher = &openrtb2.Publisher{ID: pubID} + } + request.Site = &siteCopy + } else if request.App != nil { + appCopy := *request.App + if appCopy.Publisher != nil { + publisherCopy := *appCopy.Publisher + if pubID != "" { + publisherCopy.ID = pubID + } + appCopy.Publisher = &publisherCopy + } else { + appCopy.Publisher = &openrtb2.Publisher{ID: pubID} + } + request.App = &appCopy + } + + errs := make([]error, 0, len(request.Imp)) + reqJSON, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("x-integration-type", PREBID_INTEGRATION_TYPE) + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.bidderEndpoint, + Body: reqJSON, + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), + }}, errs +} + +func getPublisherId(impressions []openrtb2.Imp) (string, error) { + for _, imp := range impressions { + + var bidderExt ExtImpBidderTheTradeDesk + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { + return "", err + } + + var ttdExt openrtb_ext.ExtImpTheTradeDesk + if err := jsonutil.Unmarshal(bidderExt.Bidder, &ttdExt); err != nil { + return "", err + } + + if ttdExt.PublisherId != "" { + return ttdExt.PublisherId, nil + } + } + return "", nil +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(response) { + return adapters.NewBidderResponse(), nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil { + return nil, []error{err} + } + + var bidResponse openrtb2.BidResponse + if err := jsonutil.Unmarshal(response.Body, &bidResponse); err != nil { + return nil, []error{err} + } + + bidderResponse := adapters.NewBidderResponse() + bidderResponse.Currency = bidResponse.Cur + + for _, seatBid := range bidResponse.SeatBid { + for _, bid := range seatBid.Bid { + bid := bid + + bidType, err := getBidType(bid.MType) + + if err != nil { + return nil, []error{err} + } + + b := &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + } + bidderResponse.Bids = append(bidderResponse.Bids, b) + } + } + + return bidderResponse, nil +} + +func getBidType(markupType openrtb2.MarkupType) (openrtb_ext.BidType, error) { + switch markupType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return "", fmt.Errorf("unsupported mtype: %d", markupType) + } +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + template, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + + if len(config.ExtraAdapterInfo) > 0 { + isValidEndpoint, err := regexp.Match("([a-z]+)$", []byte(config.ExtraAdapterInfo)) + if !isValidEndpoint || err != nil { + return nil, errors.New("ExtraAdapterInfo must be a simple string provided by TheTradeDesk") + } + } + + urlParams := macros.EndpointTemplateParams{SupplyId: config.ExtraAdapterInfo} + bidderEndpoint, err := macros.ResolveMacros(template, urlParams) + + if err != nil { + return nil, fmt.Errorf("unable to resolve endpoint macros: %v", err) + } + + return &adapter{ + bidderEndpoint: bidderEndpoint, + }, nil +} diff --git a/adapters/thetradedesk/thetradedesk_test.go b/adapters/thetradedesk/thetradedesk_test.go new file mode 100644 index 00000000000..658d812bcac --- /dev/null +++ b/adapters/thetradedesk/thetradedesk_test.go @@ -0,0 +1,385 @@ +package thetradedesk + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderTheTradeDesk, config.Adapter{ + Endpoint: "{{Malformed}}"}, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + assert.Error(t, buildErr) +} + +func TestBadConfig(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderTheTradeDesk, config.Adapter{ + Endpoint: `http://it.doesnt.matter/bid`, + ExtraAdapterInfo: "12365217635", + }, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + assert.Error(t, buildErr) +} + +func TestCorrectConfig(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderTheTradeDesk, config.Adapter{ + Endpoint: `http://it.doesnt.matter/bid`, + ExtraAdapterInfo: `abcde`, + }, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + assert.NoError(t, buildErr) + assert.NotNil(t, bidder) +} + +func TestEmptyConfig(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderTheTradeDesk, config.Adapter{ + Endpoint: `https://direct.adsrvr.org/bid/bidder/{{.SupplyId}}`, + ExtraAdapterInfo: `ttd`, + }, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + assert.NoError(t, buildErr) + assert.NotNil(t, bidder) +} + +func TestJsonSamples(t *testing.T) { + bidder, err := Builder( + openrtb_ext.BidderTheTradeDesk, + config.Adapter{Endpoint: "https://direct.adsrvr.org/bid/bidder/{{.SupplyId}}", ExtraAdapterInfo: "ttd"}, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "1"}, + ) + assert.Nil(t, err) + adapterstest.RunJSONBidderTest(t, "thetradedesktest", bidder) +} + +func TestGetBidType(t *testing.T) { + type args struct { + markupType openrtb2.MarkupType + } + tests := []struct { + name string + args args + markupType openrtb2.MarkupType + expectedBidTypeId openrtb_ext.BidType + wantErr bool + }{ + { + name: "banner", + args: args{ + markupType: openrtb2.MarkupBanner, + }, + expectedBidTypeId: openrtb_ext.BidTypeBanner, + wantErr: false, + }, + { + name: "video", + args: args{ + markupType: openrtb2.MarkupVideo, + }, + expectedBidTypeId: openrtb_ext.BidTypeVideo, + wantErr: false, + }, + { + name: "native", + args: args{ + markupType: openrtb2.MarkupNative, + }, + expectedBidTypeId: openrtb_ext.BidTypeNative, + wantErr: false, + }, + { + name: "invalid", + args: args{ + markupType: -1, + }, + expectedBidTypeId: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + bidType, err := getBidType(tt.args.markupType) + assert.Equal(t, tt.wantErr, err != nil) + assert.Equal(t, tt.expectedBidTypeId, bidType) + }) + } +} + +func TestGetPublisherId(t *testing.T) { + type args struct { + impressions []openrtb2.Imp + } + tests := []struct { + name string + args args + expectedPublisherId string + wantErr bool + }{ + { + name: "valid_publisher_Id", + args: args{ + impressions: []openrtb2.Imp{ + { + Video: &openrtb2.Video{}, + Ext: json.RawMessage(`{"bidder":{"publisherId":"1"}}`), + }, + }, + }, + expectedPublisherId: "1", + wantErr: false, + }, + { + name: "multiple_valid_publisher_Id", + args: args{ + impressions: []openrtb2.Imp{ + { + Video: &openrtb2.Video{}, + Ext: json.RawMessage(`{"bidder":{"publisherId":"1"}}`), + }, + { + Video: &openrtb2.Video{}, + Ext: json.RawMessage(`{"bidder":{"publisherId":"2"}}`), + }, + }, + }, + expectedPublisherId: "1", + wantErr: false, + }, + { + name: "not_publisherId_present", + args: args{ + impressions: []openrtb2.Imp{ + { + Video: &openrtb2.Video{}, + Ext: json.RawMessage(`{"bidder":{}}`), + }, + }, + }, + expectedPublisherId: "", + wantErr: false, + }, + { + name: "nil_publisherId_present", + args: args{ + impressions: []openrtb2.Imp{ + { + Video: &openrtb2.Video{}, + Ext: json.RawMessage(`{"bidder":{"publisherId":""}}`), + }, + }, + }, + expectedPublisherId: "", + wantErr: false, + }, + { + name: "no_impressions", + args: args{ + impressions: []openrtb2.Imp{}, + }, + expectedPublisherId: "", + wantErr: false, + }, + { + name: "invalid_bidder_object", + args: args{ + impressions: []openrtb2.Imp{ + { + Video: &openrtb2.Video{}, + Ext: json.RawMessage(`{"bidder":{"doesnotexistprop":""}}`), + }, + }, + }, + expectedPublisherId: "", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + publisherId, err := getPublisherId(tt.args.impressions) + assert.Equal(t, tt.wantErr, err != nil) + assert.Equal(t, tt.expectedPublisherId, publisherId) + }) + } +} + +func TestTheTradeDeskAdapter_MakeRequests(t *testing.T) { + type fields struct { + URI string + } + type args struct { + request *openrtb2.BidRequest + reqInfo *adapters.ExtraRequestInfo + } + tests := []struct { + name string + fields fields + args args + expectedReqData []*adapters.RequestData + wantErr bool + }{ + { + name: "invalid_bidderparams", + args: args{ + request: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"bidderparams":{:"123"}}}`)}, + }, + wantErr: true, + }, + { + name: "request_with_App", + args: args{ + request: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Ext: json.RawMessage(`{"prebid":{"bidderparams":{"wrapper":"123"}}}`), + }, + }, + wantErr: false, + }, + { + name: "request_with_App_and_publisher", + args: args{ + request: &openrtb2.BidRequest{ + App: &openrtb2.App{Publisher: &openrtb2.Publisher{}}, + Ext: json.RawMessage(`{"prebid":{"bidderparams":{"wrapper":"123"}}}`), + }, + }, + wantErr: false, + }, + { + name: "request_with_Site", + args: args{ + request: &openrtb2.BidRequest{ + Site: &openrtb2.Site{}, + Ext: json.RawMessage(`{"prebid":{"bidderparams":{"wrapper":"123"}}}`), + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &adapter{ + bidderEndpoint: tt.fields.URI, + } + gotReqData, gotErr := a.MakeRequests(tt.args.request, tt.args.reqInfo) + assert.Equal(t, tt.wantErr, len(gotErr) != 0) + if tt.wantErr == false { + assert.NotNil(t, gotReqData) + } + }) + } +} + +func TestTheTradeDeskAdapter_MakeBids(t *testing.T) { + type fields struct { + URI string + } + type args struct { + internalRequest *openrtb2.BidRequest + externalRequest *adapters.RequestData + response *adapters.ResponseData + } + tests := []struct { + name string + fields fields + args args + wantErr []error + wantResp *adapters.BidderResponse + }{ + { + name: "happy_path_valid_response_with_all_bid_params", + args: args{ + response: &adapters.ResponseData{ + StatusCode: http.StatusOK, + Body: []byte(`{"id": "test-request-id", "seatbid":[{"seat": "958", "bid":[{"mtype": 1, "id": "7706636740145184841", "impid": "test-imp-id", "price": 0.500000, "adid": "29681110", "adm": "some-test-ad", "adomain":["ttd.com"], "crid": "29681110", "h": 250, "w": 300, "dealid": "testdeal", "ext":{"dspid": 6, "deal_channel": 1, "prebiddealpriority": 1}}]}], "bidid": "5778926625248726496", "cur": "USD"}`), + }, + }, + wantErr: nil, + wantResp: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "7706636740145184841", + ImpID: "test-imp-id", + Price: 0.500000, + AdID: "29681110", + AdM: "some-test-ad", + ADomain: []string{"ttd.com"}, + CrID: "29681110", + H: 250, + W: 300, + DealID: "testdeal", + Ext: json.RawMessage(`{"dspid": 6, "deal_channel": 1, "prebiddealpriority": 1}`), + MType: openrtb2.MarkupBanner, + }, + BidType: openrtb_ext.BidTypeBanner, + }, + }, + Currency: "USD", + }, + }, + { + name: "ignore_invalid_prebiddealpriority", + args: args{ + response: &adapters.ResponseData{ + StatusCode: http.StatusOK, + Body: []byte(`{"id": "test-request-id", "seatbid":[{"seat": "958", "bid":[{"mtype": 2, "id": "7706636740145184841", "impid": "test-imp-id", "price": 0.500000, "adid": "29681110", "adm": "some-test-ad", "adomain":["ttd.com"], "crid": "29681110", "h": 250, "w": 300, "dealid": "testdeal", "ext":{"dspid": 6, "deal_channel": 1, "prebiddealpriority": -1}}]}], "bidid": "5778926625248726496", "cur": "USD"}`), + }, + }, + wantErr: nil, + wantResp: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "7706636740145184841", + ImpID: "test-imp-id", + Price: 0.500000, + AdID: "29681110", + AdM: "some-test-ad", + ADomain: []string{"ttd.com"}, + CrID: "29681110", + H: 250, + W: 300, + DealID: "testdeal", + Ext: json.RawMessage(`{"dspid": 6, "deal_channel": 1, "prebiddealpriority": -1}`), + MType: openrtb2.MarkupVideo, + }, + BidType: openrtb_ext.BidTypeVideo, + }, + }, + Currency: "USD", + }, + }, + { + name: "no_content_response", + args: args{ + response: &adapters.ResponseData{ + StatusCode: http.StatusNoContent, + Body: nil, + }, + }, + wantErr: nil, + wantResp: adapters.NewBidderResponse(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &adapter{ + bidderEndpoint: tt.fields.URI, + } + gotResp, gotErr := a.MakeBids(tt.args.internalRequest, tt.args.externalRequest, tt.args.response) + assert.Equal(t, tt.wantErr, gotErr, gotErr) + assert.Equal(t, tt.wantResp, gotResp) + }) + } +} diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-inapp.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-inapp.json new file mode 100644 index 00000000000..e10445382c8 --- /dev/null +++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-inapp.json @@ -0,0 +1,144 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle", + "publisher": { + "id": "this_id_will_be_replaced" + } + }, + "device": { + "ifa": "test-ifa-123456", + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 100, + "h": 150 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://direct.adsrvr.org/bid/bidder/ttd", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Integration-Type": ["1"] + }, + "body": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle", + "publisher": { + "id": "123456" + } + }, + "device": { + "ifa": "test-ifa-123456", + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner", + "networkName": "TheTradeDesk" + } + }, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner", + "networkName": "TheTradeDesk" + } + }, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids-and-formats.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids-and-formats.json new file mode 100644 index 00000000000..0180d48dde2 --- /dev/null +++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids-and-formats.json @@ -0,0 +1,231 @@ +{ + "mockBidRequest": { + "id": "test-request-id-multiple-bids", + "site": { + "id": "site-id", + "page": "ttd.com", + "publisher": { + "id": "123456" + } + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 500, + "h": 300 + } + ], + "w": 55, + "h": 33 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + }, + { + "id": "test-imp-id2", + "banner": { + "format": [ + { + "w": 900, + "h": 450 + }, + { + "w": 500, + "h": 300 + } + ], + "w": 88, + "h": 99 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://direct.adsrvr.org/bid/bidder/ttd", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Integration-Type": ["1"] + }, + "body": { + "id": "test-request-id-multiple-bids", + "site": { + "id": "site-id", + "page": "ttd.com", + "publisher": { + "id": "123456" + } + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 500, + "h": 300 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + }, + { + "id": "test-imp-id2", + "banner": { + "format": [ + { + "w": 900, + "h": 450 + }, + { + "w": 500, + "h": 300 + } + ], + "w": 900, + "h": 450 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "impIDs":["test-imp-id","test-imp-id2"] + }, + "mockResponse": { + "status": 200, + "body": { + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + }, + "mtype": 1 + } + ] + }, + { + "bid": [ + { + "id": "test-slot-id2", + "impid": "test-imp-id2", + "price": 0.5, + "crid": "creative-123", + "adm": "", + "w": 900, + "h": 450, + "ext": { + "prebid": { + "type": "banner" + } + }, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + }, + "mtype": 1 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id2", + "impid": "test-imp-id2", + "price": 0.5, + "crid": "creative-123", + "adm": "", + "w": 900, + "h": 450, + "ext": { + "prebid": { + "type": "banner" + } + }, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids.json new file mode 100644 index 00000000000..4d46fa6907b --- /dev/null +++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner-multiple-bids.json @@ -0,0 +1,215 @@ +{ + "mockBidRequest": { + "id": "test-request-id-multiple-bids", + "site": { + "id": "site-id", + "page": "ttd.com", + "publisher": { + "id": "123456" + } + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + }, + { + "id": "test-imp-id2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://direct.adsrvr.org/bid/bidder/ttd", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Integration-Type": ["1"] + }, + "body": { + "id": "test-request-id-multiple-bids", + "site": { + "id": "site-id", + "page": "ttd.com", + "publisher": { + "id": "123456" + } + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + }, + { + "id": "test-imp-id2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "impIDs":["test-imp-id","test-imp-id2"] + }, + "mockResponse": { + "status": 200, + "body": { + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + }, + "mtype": 1 + } + ] + }, + { + "bid": [ + { + "id": "test-slot-id2", + "impid": "test-imp-id2", + "price": 0.5, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + }, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + }, + "mtype": 1 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id2", + "impid": "test-imp-id2", + "price": 0.5, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + }, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner.json new file mode 100644 index 00000000000..4ac6acb0556 --- /dev/null +++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-banner.json @@ -0,0 +1,142 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "ttd.com", + "publisher": { + "id": "123456" + } + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 100, + "h": 150 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://direct.adsrvr.org/bid/bidder/ttd", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Integration-Type": ["1"] + }, + "body": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "ttd.com", + "publisher": { + "id": "123456" + } + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + }, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + }, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-empty-publisherId.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-empty-publisherId.json new file mode 100644 index 00000000000..ef6f6695553 --- /dev/null +++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-empty-publisherId.json @@ -0,0 +1,142 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "ttd.com", + "publisher": { + "id": "did_not_override" + } + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://direct.adsrvr.org/bid/bidder/ttd", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Integration-Type": ["1"] + }, + "body": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "ttd.com", + "publisher": { + "id": "did_not_override" + } + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + }, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + }, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-banner.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-banner.json new file mode 100644 index 00000000000..10286c96081 --- /dev/null +++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-banner.json @@ -0,0 +1,151 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "ttd.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://direct.adsrvr.org/bid/bidder/ttd", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Integration-Type": ["1"] + }, + "body": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "ttd.com", + "publisher": { + "id": "123456" + } + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "h": 250, + "w": 300, + "ext": { + "prebid": { + "type": "banner" + } + }, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "h": 250, + "w": 300, + "ext": { + "prebid": { + "type": "banner" + } + }, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-video.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-video.json new file mode 100644 index 00000000000..e8f4bbca57c --- /dev/null +++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-multi-type-video.json @@ -0,0 +1,151 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "ttd.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://direct.adsrvr.org/bid/bidder/ttd", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Integration-Type": ["1"] + }, + "body": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "ttd.com", + "publisher": { + "id": "123456" + } + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "some-test-ad-vast", + "h": 250, + "w": 300, + "ext": { + "prebid": { + "type": "video" + } + }, + "mtype": 2 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "some-test-ad-vast", + "h": 250, + "w": 300, + "ext": { + "prebid": { + "type": "video" + } + }, + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} + diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-native.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-native.json new file mode 100644 index 00000000000..4d1b77ab0fb --- /dev/null +++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-native.json @@ -0,0 +1,115 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "ttd.com" + }, + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://direct.adsrvr.org/bid/bidder/ttd", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Integration-Type": ["1"] + }, + "body": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "ttd.com", + "publisher": { + "id": "123456" + } + }, + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "h": 250, + "w": 300, + "ext": { + "prebid": { + "type": "native" + } + }, + "mtype": 4 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "h": 250, + "w": 300, + "ext": { + "prebid": { + "type": "native" + } + }, + "mtype": 4 + }, + "type": "native" + } + ] + } + ] +} + diff --git a/adapters/thetradedesk/thetradedesktest/exemplary/simple-video.json b/adapters/thetradedesk/thetradedesktest/exemplary/simple-video.json new file mode 100644 index 00000000000..5dc945bab1f --- /dev/null +++ b/adapters/thetradedesk/thetradedesktest/exemplary/simple-video.json @@ -0,0 +1,154 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "ttd.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "pbs-local/preroll", + "video": { + "minduration": 0, + "maxduration": 60, + "api": [1,2], + "mimes": [ + "video/mp4", + "video/webm", + "application/javascript" + ], + "placement": 1, + "protocols": [2,3,4,5,6], + "w": 300, + "h": 250, + "playbackmethod": [1,2,3,4,5,6], + "plcmt": 1, + "skip": 1 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://direct.adsrvr.org/bid/bidder/ttd", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Integration-Type": ["1"] + }, + "body": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "ttd.com", + "publisher": { + "id": "123456" + } + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "pbs-local/preroll", + "video": { + "maxduration": 60, + "api": [1,2], + "mimes": [ + "video/mp4", + "video/webm", + "application/javascript" + ], + "placement": 1, + "protocols": [2,3,4,5,6], + "w": 300, + "h": 250, + "playbackmethod": [1,2,3,4,5,6], + "plcmt": 1, + "skip": 1 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "some-test-ad-vast", + "h": 250, + "w": 300, + "ext": { + "prebid": { + "type": "video" + } + }, + "mtype": 2 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "some-test-ad-vast", + "h": 250, + "w": 300, + "ext": { + "prebid": { + "type": "video" + } + }, + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} + diff --git a/adapters/thetradedesk/thetradedesktest/supplemental/200-response-from-target.json b/adapters/thetradedesk/thetradedesktest/supplemental/200-response-from-target.json new file mode 100644 index 00000000000..fdbe15ac48e --- /dev/null +++ b/adapters/thetradedesk/thetradedesktest/supplemental/200-response-from-target.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://direct.adsrvr.org/bid/bidder/ttd", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Integration-Type": ["1"] + }, + "body": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle", + "publisher": { + "id": "123456" + } + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "expect { or n, but found", + "comparison": "startswith" + } + ] + } diff --git a/adapters/thetradedesk/thetradedesktest/supplemental/204-response-from-target.json b/adapters/thetradedesk/thetradedesktest/supplemental/204-response-from-target.json new file mode 100644 index 00000000000..a329982ea50 --- /dev/null +++ b/adapters/thetradedesk/thetradedesktest/supplemental/204-response-from-target.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://direct.adsrvr.org/bid/bidder/ttd", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Integration-Type": ["1"] + }, + "body": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle", + "publisher": { + "id": "123456" + } + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ] + } diff --git a/adapters/thetradedesk/thetradedesktest/supplemental/400-response-from-target.json b/adapters/thetradedesk/thetradedesktest/supplemental/400-response-from-target.json new file mode 100644 index 00000000000..ad5ffc62b51 --- /dev/null +++ b/adapters/thetradedesk/thetradedesktest/supplemental/400-response-from-target.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://direct.adsrvr.org/bid/bidder/ttd", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Integration-Type": ["1"] + }, + "body": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle", + "publisher": { + "id": "123456" + } + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] + } diff --git a/adapters/thetradedesk/thetradedesktest/supplemental/500-response-from-target.json b/adapters/thetradedesk/thetradedesktest/supplemental/500-response-from-target.json new file mode 100644 index 00000000000..f2ccb342113 --- /dev/null +++ b/adapters/thetradedesk/thetradedesktest/supplemental/500-response-from-target.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://direct.adsrvr.org/bid/bidder/ttd", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Integration-Type": ["1"] + }, + "body": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle", + "publisher": { + "id": "123456" + } + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 500 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] + } diff --git a/adapters/thetradedesk/thetradedesktest/supplemental/invalid-mtype.json b/adapters/thetradedesk/thetradedesktest/supplemental/invalid-mtype.json new file mode 100644 index 00000000000..52b392eb835 --- /dev/null +++ b/adapters/thetradedesk/thetradedesktest/supplemental/invalid-mtype.json @@ -0,0 +1,86 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://direct.adsrvr.org/bid/bidder/ttd", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Integration-Type": ["1"] + }, + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "publisherId": "123456" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "currency": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "h": 250, + "w": 300, + "ext": { + "prebid": { + "type": "native" + } + }, + "mtype": -1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "unsupported mtype: -1", + "comparison": "literal" + } + ] +} + diff --git a/adapters/thetradedesk/thetradedesktest/supplemental/invalid-publisher.json b/adapters/thetradedesk/thetradedesktest/supplemental/invalid-publisher.json new file mode 100644 index 00000000000..9e2426d79b2 --- /dev/null +++ b/adapters/thetradedesk/thetradedesktest/supplemental/invalid-publisher.json @@ -0,0 +1,45 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle", + "publisher": { + "id": "this_id_will_be_replaced" + } + }, + "device": { + "ifa": "test-ifa-123456", + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherId": 123456 + } + } + } + ] + }, + "httpCalls": [], + "expectedMakeRequestsErrors": [ + { + "value": "cannot unmarshal openrtb_ext.ExtImpTheTradeDesk.PublisherId: expects \" or n, but found 1", + "comparison": "literal" + } + ] +} + diff --git a/adapters/tpmn/params_test.go b/adapters/tpmn/params_test.go index 7bd7c478638..674da5fb65a 100644 --- a/adapters/tpmn/params_test.go +++ b/adapters/tpmn/params_test.go @@ -2,8 +2,9 @@ package tpmn import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/tpmn/tpmn.go b/adapters/tpmn/tpmn.go index 7afe94e5f79..262fc28ac6e 100644 --- a/adapters/tpmn/tpmn.go +++ b/adapters/tpmn/tpmn.go @@ -6,10 +6,11 @@ import ( "net/http" "strings" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) // TpmnAdapter struct @@ -41,6 +42,7 @@ func (rcv *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters Uri: rcv.uri, Body: requestBodyJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }}, errs } @@ -82,7 +84,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData } var response openrtb2.BidResponse - if err := json.Unmarshal(responseData.Body, &response); err != nil { + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { return nil, []error{fmt.Errorf("bid response unmarshal: %v", err)} } diff --git a/adapters/tpmn/tpmn_test.go b/adapters/tpmn/tpmn_test.go index 6fbd85936f1..865771e89ca 100644 --- a/adapters/tpmn/tpmn_test.go +++ b/adapters/tpmn/tpmn_test.go @@ -3,9 +3,9 @@ package tpmn import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/tpmn/tpmntest/exemplary/simple-banner.json b/adapters/tpmn/tpmntest/exemplary/simple-banner.json index 197d03b174e..a6618a9297b 100644 --- a/adapters/tpmn/tpmntest/exemplary/simple-banner.json +++ b/adapters/tpmn/tpmntest/exemplary/simple-banner.json @@ -71,7 +71,8 @@ "ip": "123.123.123.123", "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/tpmn/tpmntest/exemplary/simple-native.json b/adapters/tpmn/tpmntest/exemplary/simple-native.json index 1880c74ac7e..1e62ab47183 100644 --- a/adapters/tpmn/tpmntest/exemplary/simple-native.json +++ b/adapters/tpmn/tpmntest/exemplary/simple-native.json @@ -55,7 +55,8 @@ "ip": "123.123.123.123", "ua": "iPad" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/tpmn/tpmntest/exemplary/simple-site-banner.json b/adapters/tpmn/tpmntest/exemplary/simple-site-banner.json index 8f7c5d59301..ddd031324d4 100644 --- a/adapters/tpmn/tpmntest/exemplary/simple-site-banner.json +++ b/adapters/tpmn/tpmntest/exemplary/simple-site-banner.json @@ -67,7 +67,8 @@ "device": { "ip": "123.123.123.123" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/tpmn/tpmntest/exemplary/simple-site-native.json b/adapters/tpmn/tpmntest/exemplary/simple-site-native.json index 20e6c23e966..1cb63244241 100644 --- a/adapters/tpmn/tpmntest/exemplary/simple-site-native.json +++ b/adapters/tpmn/tpmntest/exemplary/simple-site-native.json @@ -58,7 +58,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/tpmn/tpmntest/exemplary/simple-site-video.json b/adapters/tpmn/tpmntest/exemplary/simple-site-video.json index 7b2375cd07a..77be3f7eb94 100644 --- a/adapters/tpmn/tpmntest/exemplary/simple-site-video.json +++ b/adapters/tpmn/tpmntest/exemplary/simple-site-video.json @@ -76,7 +76,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/tpmn/tpmntest/exemplary/simple-video.json b/adapters/tpmn/tpmntest/exemplary/simple-video.json index 505b6167069..50080a42434 100644 --- a/adapters/tpmn/tpmntest/exemplary/simple-video.json +++ b/adapters/tpmn/tpmntest/exemplary/simple-video.json @@ -69,7 +69,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/tpmn/tpmntest/supplemental/bad-imp-ext.json b/adapters/tpmn/tpmntest/supplemental/bad-imp-ext.json index 3bbb23f95a4..aaf0a5f5a2f 100644 --- a/adapters/tpmn/tpmntest/supplemental/bad-imp-ext.json +++ b/adapters/tpmn/tpmntest/supplemental/bad-imp-ext.json @@ -65,7 +65,8 @@ "ip": "123.123.123.123", "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/tpmn/tpmntest/supplemental/bad_response.json b/adapters/tpmn/tpmntest/supplemental/bad_response.json index 12c3a72d49f..c8096e9df2a 100644 --- a/adapters/tpmn/tpmntest/supplemental/bad_response.json +++ b/adapters/tpmn/tpmntest/supplemental/bad_response.json @@ -70,7 +70,8 @@ "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -80,7 +81,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "bid response unmarshal: json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "value": "bid response unmarshal: expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/tpmn/tpmntest/supplemental/no-imp-ext.json b/adapters/tpmn/tpmntest/supplemental/no-imp-ext.json index 8bcbb93d004..9bb91e4c167 100644 --- a/adapters/tpmn/tpmntest/supplemental/no-imp-ext.json +++ b/adapters/tpmn/tpmntest/supplemental/no-imp-ext.json @@ -63,7 +63,8 @@ "ip": "123.123.123.123", "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -73,7 +74,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "bid response unmarshal: json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "value": "bid response unmarshal: expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/tpmn/tpmntest/supplemental/status-204.json b/adapters/tpmn/tpmntest/supplemental/status-204.json index fdcd3f7fd55..6925c56a6f7 100644 --- a/adapters/tpmn/tpmntest/supplemental/status-204.json +++ b/adapters/tpmn/tpmntest/supplemental/status-204.json @@ -69,7 +69,8 @@ "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/tpmn/tpmntest/supplemental/status-404.json b/adapters/tpmn/tpmntest/supplemental/status-404.json index 74ced15217c..59e2265b7c5 100644 --- a/adapters/tpmn/tpmntest/supplemental/status-404.json +++ b/adapters/tpmn/tpmntest/supplemental/status-404.json @@ -69,7 +69,8 @@ "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 404, diff --git a/adapters/trafficgate/params_test.go b/adapters/trafficgate/params_test.go index 4dc2c792bc9..0667a8bc5a1 100644 --- a/adapters/trafficgate/params_test.go +++ b/adapters/trafficgate/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // TestValidParams makes sure that the trafficgate schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/trafficgate/trafficgate.go b/adapters/trafficgate/trafficgate.go index 3c9ebe9ba98..d4d181399dd 100644 --- a/adapters/trafficgate/trafficgate.go +++ b/adapters/trafficgate/trafficgate.go @@ -6,12 +6,13 @@ import ( "net/http" "text/template" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { @@ -61,7 +62,8 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E Method: "POST", Uri: url, Body: reqJson, - Headers: headers} + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp)} requests = append(requests, &request) } @@ -90,7 +92,7 @@ func (a *adapter) MakeBids( var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: err.Error(), }} @@ -102,14 +104,14 @@ func (a *adapter) MakeBids( for _, seatBid := range bidResp.SeatBid { for i := range seatBid.Bid { var bidExt BidResponseExt - if err := json.Unmarshal(seatBid.Bid[i].Ext, &bidExt); err != nil { + if err := jsonutil.Unmarshal(seatBid.Bid[i].Ext, &bidExt); err != nil { return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Missing response ext"), + Message: "Missing response ext", }} } if len(bidExt.Prebid.Type) < 1 { return nil, []error{&errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unable to read bid.ext.prebid.type"), + Message: "Unable to read bid.ext.prebid.type", }} } bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ @@ -140,15 +142,15 @@ func splitImpressions(imps []openrtb2.Imp) (map[openrtb_ext.ExtImpTrafficGate][] func getBidderParams(imp *openrtb2.Imp) (*openrtb_ext.ExtImpTrafficGate, error) { var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("Missing bidder ext"), + Message: "Missing bidder ext", } } var TrafficGateExt openrtb_ext.ExtImpTrafficGate - if err := json.Unmarshal(bidderExt.Bidder, &TrafficGateExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &TrafficGateExt); err != nil { return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("Bidder parameters required"), + Message: "Bidder parameters required", } } diff --git a/adapters/trafficgate/trafficgate_test.go b/adapters/trafficgate/trafficgate_test.go index 326c50523fe..df932538d96 100644 --- a/adapters/trafficgate/trafficgate_test.go +++ b/adapters/trafficgate/trafficgate_test.go @@ -3,9 +3,9 @@ package trafficgate import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/trafficgate/trafficgatetest/exemplary/multiple-imps.json b/adapters/trafficgate/trafficgatetest/exemplary/multiple-imps.json index 14f77f62911..d054d88ad3e 100644 --- a/adapters/trafficgate/trafficgatetest/exemplary/multiple-imps.json +++ b/adapters/trafficgate/trafficgatetest/exemplary/multiple-imps.json @@ -89,7 +89,8 @@ } } ] - } + }, + "impIDs":["test-imp-id","test-imp-id2"] }, "mockResponse": { "status": 200, diff --git a/adapters/trafficgate/trafficgatetest/exemplary/simple-audio.json b/adapters/trafficgate/trafficgatetest/exemplary/simple-audio.json index 4cbe9f63c50..12409c26fa9 100644 --- a/adapters/trafficgate/trafficgatetest/exemplary/simple-audio.json +++ b/adapters/trafficgate/trafficgatetest/exemplary/simple-audio.json @@ -53,7 +53,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/trafficgate/trafficgatetest/exemplary/simple-banner.json b/adapters/trafficgate/trafficgatetest/exemplary/simple-banner.json index 7f3c7abfafe..e65f889543e 100644 --- a/adapters/trafficgate/trafficgatetest/exemplary/simple-banner.json +++ b/adapters/trafficgate/trafficgatetest/exemplary/simple-banner.json @@ -71,7 +71,8 @@ "ip": "123.123.123.123", "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/trafficgate/trafficgatetest/exemplary/simple-native.json b/adapters/trafficgate/trafficgatetest/exemplary/simple-native.json index c2482c860ba..f289fff5020 100644 --- a/adapters/trafficgate/trafficgatetest/exemplary/simple-native.json +++ b/adapters/trafficgate/trafficgatetest/exemplary/simple-native.json @@ -55,7 +55,8 @@ "ip": "123.123.123.123", "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/trafficgate/trafficgatetest/exemplary/simple-video.json b/adapters/trafficgate/trafficgatetest/exemplary/simple-video.json index 61a6d6ef314..c0448229c88 100644 --- a/adapters/trafficgate/trafficgatetest/exemplary/simple-video.json +++ b/adapters/trafficgate/trafficgatetest/exemplary/simple-video.json @@ -59,7 +59,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/trafficgate/trafficgatetest/exemplary/simple-web-banner.json b/adapters/trafficgate/trafficgatetest/exemplary/simple-web-banner.json index f46fde30183..38085564feb 100644 --- a/adapters/trafficgate/trafficgatetest/exemplary/simple-web-banner.json +++ b/adapters/trafficgate/trafficgatetest/exemplary/simple-web-banner.json @@ -68,7 +68,8 @@ "device": { "ip": "123.123.123.123" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/trafficgate/trafficgatetest/supplemental/bad_bidtype_response.json b/adapters/trafficgate/trafficgatetest/supplemental/bad_bidtype_response.json index 177a7cf261c..706825fb491 100644 --- a/adapters/trafficgate/trafficgatetest/supplemental/bad_bidtype_response.json +++ b/adapters/trafficgate/trafficgatetest/supplemental/bad_bidtype_response.json @@ -70,7 +70,8 @@ "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/trafficgate/trafficgatetest/supplemental/bad_ext_response.json b/adapters/trafficgate/trafficgatetest/supplemental/bad_ext_response.json index caea968d195..fb792e5f4e3 100644 --- a/adapters/trafficgate/trafficgatetest/supplemental/bad_ext_response.json +++ b/adapters/trafficgate/trafficgatetest/supplemental/bad_ext_response.json @@ -70,7 +70,8 @@ "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/trafficgate/trafficgatetest/supplemental/bad_response.json b/adapters/trafficgate/trafficgatetest/supplemental/bad_response.json index 60c9a43eed1..64cccbf6905 100644 --- a/adapters/trafficgate/trafficgatetest/supplemental/bad_response.json +++ b/adapters/trafficgate/trafficgatetest/supplemental/bad_response.json @@ -70,7 +70,8 @@ "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -80,7 +81,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/trafficgate/trafficgatetest/supplemental/bad_status_code.json b/adapters/trafficgate/trafficgatetest/supplemental/bad_status_code.json index 41207652f25..dcd0359eaf5 100644 --- a/adapters/trafficgate/trafficgatetest/supplemental/bad_status_code.json +++ b/adapters/trafficgate/trafficgatetest/supplemental/bad_status_code.json @@ -64,7 +64,8 @@ "bundle": "com.wls.testwlsapplication" }, "device": {} - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/trafficgate/trafficgatetest/supplemental/status-204.json b/adapters/trafficgate/trafficgatetest/supplemental/status-204.json index 79235df21d3..4b682056324 100644 --- a/adapters/trafficgate/trafficgatetest/supplemental/status-204.json +++ b/adapters/trafficgate/trafficgatetest/supplemental/status-204.json @@ -70,7 +70,8 @@ "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" } - } + }, + "impIDs":["test-imp-id"] }, "expectedBidResponses": [], "mockResponse": { diff --git a/adapters/trafficgate/trafficgatetest/supplemental/status-404.json b/adapters/trafficgate/trafficgatetest/supplemental/status-404.json index 90bbd7647ba..bebbed36f25 100644 --- a/adapters/trafficgate/trafficgatetest/supplemental/status-404.json +++ b/adapters/trafficgate/trafficgatetest/supplemental/status-404.json @@ -70,7 +70,8 @@ "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 404, diff --git a/adapters/triplelift/triplelift.go b/adapters/triplelift/triplelift.go index 0e7fbe4a462..c5d89cd2507 100644 --- a/adapters/triplelift/triplelift.go +++ b/adapters/triplelift/triplelift.go @@ -5,11 +5,12 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type TripleliftAdapter struct { @@ -36,10 +37,10 @@ func processImp(imp *openrtb2.Imp) error { // get the triplelift extension var ext adapters.ExtImpBidder var tlext openrtb_ext.ExtImpTriplelift - if err := json.Unmarshal(imp.Ext, &ext); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &ext); err != nil { return err } - if err := json.Unmarshal(ext.Bidder, &tlext); err != nil { + if err := jsonutil.Unmarshal(ext.Bidder, &tlext); err != nil { return err } if imp.Banner == nil && imp.Video == nil { @@ -90,7 +91,8 @@ func (a *TripleliftAdapter) MakeRequests(request *openrtb2.BidRequest, extra *ad Method: "POST", Uri: ad, Body: reqJSON, - Headers: headers}) + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(tlRequest.Imp)}) return reqs, errs } @@ -117,7 +119,7 @@ func (a *TripleliftAdapter) MakeBids(internalRequest *openrtb2.BidRequest, exter return nil, []error{fmt.Errorf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)} } var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } var errs []error @@ -128,7 +130,7 @@ func (a *TripleliftAdapter) MakeBids(internalRequest *openrtb2.BidRequest, exter for i := 0; i < len(sb.Bid); i++ { bid := sb.Bid[i] var bidExt TripleliftRespExt - if err := json.Unmarshal(bid.Ext, &bidExt); err != nil { + if err := jsonutil.Unmarshal(bid.Ext, &bidExt); err != nil { errs = append(errs, err) } else { bidType := getBidType(bidExt) diff --git a/adapters/triplelift/triplelift_test.go b/adapters/triplelift/triplelift_test.go index add71b05788..1fd5fc8d559 100644 --- a/adapters/triplelift/triplelift_test.go +++ b/adapters/triplelift/triplelift_test.go @@ -3,9 +3,9 @@ package triplelift import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/triplelift/triplelifttest/exemplary/optional-params.json b/adapters/triplelift/triplelifttest/exemplary/optional-params.json index e83fd7a375e..33da6cb2968 100644 --- a/adapters/triplelift/triplelifttest/exemplary/optional-params.json +++ b/adapters/triplelift/triplelifttest/exemplary/optional-params.json @@ -56,7 +56,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/triplelift/triplelifttest/exemplary/simple-banner.json b/adapters/triplelift/triplelifttest/exemplary/simple-banner.json index 156e07e37eb..36fb2b66c98 100644 --- a/adapters/triplelift/triplelifttest/exemplary/simple-banner.json +++ b/adapters/triplelift/triplelifttest/exemplary/simple-banner.json @@ -53,7 +53,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/triplelift/triplelifttest/exemplary/simple-video.json b/adapters/triplelift/triplelifttest/exemplary/simple-video.json index 846c62b4d37..8d63b9acf3f 100644 --- a/adapters/triplelift/triplelifttest/exemplary/simple-video.json +++ b/adapters/triplelift/triplelifttest/exemplary/simple-video.json @@ -66,7 +66,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/triplelift/triplelifttest/supplemental/badext.json b/adapters/triplelift/triplelifttest/supplemental/badext.json index a36454c86d8..7c4b8190faf 100644 --- a/adapters/triplelift/triplelifttest/supplemental/badext.json +++ b/adapters/triplelift/triplelifttest/supplemental/badext.json @@ -1,7 +1,7 @@ { "expectedMakeRequestsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "value": "expect { or n, but found \"", "comparison": "literal" }, { diff --git a/adapters/triplelift/triplelifttest/supplemental/badextbidder.json b/adapters/triplelift/triplelifttest/supplemental/badextbidder.json index 744de84d45d..c5b0c1fcbed 100644 --- a/adapters/triplelift/triplelifttest/supplemental/badextbidder.json +++ b/adapters/triplelift/triplelifttest/supplemental/badextbidder.json @@ -1,7 +1,7 @@ { "expectedMakeRequestsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpTriplelift", + "value": "expect { or n, but found \"", "comparison": "literal" }, { diff --git a/adapters/triplelift/triplelifttest/supplemental/badresponseext.json b/adapters/triplelift/triplelifttest/supplemental/badresponseext.json index 6c09448fc4a..3c217f64f4e 100644 --- a/adapters/triplelift/triplelifttest/supplemental/badresponseext.json +++ b/adapters/triplelift/triplelifttest/supplemental/badresponseext.json @@ -53,7 +53,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -96,7 +97,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type triplelift.TripleliftRespExt", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/triplelift/triplelifttest/supplemental/badstatuscode.json b/adapters/triplelift/triplelifttest/supplemental/badstatuscode.json index f24eb7998ed..a4c9ee00147 100644 --- a/adapters/triplelift/triplelifttest/supplemental/badstatuscode.json +++ b/adapters/triplelift/triplelifttest/supplemental/badstatuscode.json @@ -53,7 +53,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/triplelift/triplelifttest/supplemental/notgoodstatuscode.json b/adapters/triplelift/triplelifttest/supplemental/notgoodstatuscode.json index bdcc0e3a666..f15871f5145 100644 --- a/adapters/triplelift/triplelifttest/supplemental/notgoodstatuscode.json +++ b/adapters/triplelift/triplelifttest/supplemental/notgoodstatuscode.json @@ -53,7 +53,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 302, diff --git a/adapters/triplelift/triplelifttest/supplemental/video-format-11.json b/adapters/triplelift/triplelifttest/supplemental/video-format-11.json index f7a88ec5adc..372cc51ea08 100644 --- a/adapters/triplelift/triplelifttest/supplemental/video-format-11.json +++ b/adapters/triplelift/triplelifttest/supplemental/video-format-11.json @@ -48,7 +48,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/triplelift/triplelifttest/supplemental/video-format-12.json b/adapters/triplelift/triplelifttest/supplemental/video-format-12.json index 7d5987afc10..f2b55a04013 100644 --- a/adapters/triplelift/triplelifttest/supplemental/video-format-12.json +++ b/adapters/triplelift/triplelifttest/supplemental/video-format-12.json @@ -48,7 +48,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/triplelift/triplelifttest/supplemental/video-format-17.json b/adapters/triplelift/triplelifttest/supplemental/video-format-17.json index 1fb44507e6c..26dcc943af8 100644 --- a/adapters/triplelift/triplelifttest/supplemental/video-format-17.json +++ b/adapters/triplelift/triplelifttest/supplemental/video-format-17.json @@ -48,7 +48,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/triplelift_native/params_test.go b/adapters/triplelift_native/params_test.go new file mode 100644 index 00000000000..743c3d87ed2 --- /dev/null +++ b/adapters/triplelift_native/params_test.go @@ -0,0 +1,53 @@ +package triplelift_native + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderTripleliftNative, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected triplelift native params: %s", validParam) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderTripleliftNative, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"inventoryCode":"1"}`, + `{"inventoryCode":"test"}`, + `{"inventoryCode":"test", "floor":10}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"inventoryCode":1}`, + `{"inventoryCode":""}`, + `{"inventoryCode":"1", "floor": "10"}`, +} diff --git a/adapters/triplelift_native/triplelift_native.go b/adapters/triplelift_native/triplelift_native.go index 9131c79a975..1d4c6bf638d 100644 --- a/adapters/triplelift_native/triplelift_native.go +++ b/adapters/triplelift_native/triplelift_native.go @@ -5,11 +5,12 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type TripleliftNativeAdapter struct { @@ -33,42 +34,65 @@ type TripleliftNativeExtInfo struct { PublisherWhitelistMap map[string]struct{} } +type ExtImpData struct { + TagCode string `json:"tag_code"` +} + +type ExtImp struct { + *adapters.ExtImpBidder + Data *ExtImpData `json:"data,omitempty"` +} + func getBidType(ext TripleliftRespExt) openrtb_ext.BidType { return openrtb_ext.BidTypeNative } -func processImp(imp *openrtb2.Imp) error { +func processImp(imp *openrtb2.Imp, request *openrtb2.BidRequest) error { // get the triplelift extension - var ext adapters.ExtImpBidder + var ext ExtImp var tlext openrtb_ext.ExtImpTriplelift - if err := json.Unmarshal(imp.Ext, &ext); err != nil { + + if err := jsonutil.Unmarshal(imp.Ext, &ext); err != nil { return err } - if err := json.Unmarshal(ext.Bidder, &tlext); err != nil { + if err := jsonutil.Unmarshal(ext.Bidder, &tlext); err != nil { return err } if imp.Native == nil { return fmt.Errorf("no native object specified") } - if tlext.InvCode == "" { - return fmt.Errorf("no inv_code specified") + + if ext.Data != nil && len(ext.Data.TagCode) > 0 && (msnInSite(request) || msnInApp(request)) { + imp.TagID = ext.Data.TagCode + } else { + imp.TagID = tlext.InvCode } - imp.TagID = tlext.InvCode + // floor is optional if tlext.Floor == nil { return nil } imp.BidFloor = *tlext.Floor - // no error + return nil } +// msnInApp returns whether msn.com is in request.app.publisher.domain +func msnInApp(request *openrtb2.BidRequest) bool { + return request.App != nil && request.App.Publisher != nil && request.App.Publisher.Domain == "msn.com" +} + +// msnInSite returns whether msn.com is in request.site.publisher.domain +func msnInSite(request *openrtb2.BidRequest) bool { + return request.Site != nil && request.Site.Publisher != nil && request.Site.Publisher.Domain == "msn.com" +} + // Returns the effective publisher ID func effectivePubID(pub *openrtb2.Publisher) string { if pub != nil { if pub.Ext != nil { var pubExt openrtb_ext.ExtPublisher - err := json.Unmarshal(pub.Ext, &pubExt) + err := jsonutil.Unmarshal(pub.Ext, &pubExt) if err == nil && pubExt.Prebid != nil && pubExt.Prebid.ParentAccount != nil && *pubExt.Prebid.ParentAccount != "" { return *pubExt.Prebid.ParentAccount } @@ -89,7 +113,7 @@ func (a *TripleliftNativeAdapter) MakeRequests(request *openrtb2.BidRequest, ext var validImps []openrtb2.Imp // pre-process the imps for _, imp := range tlRequest.Imp { - if err := processImp(&imp); err == nil { + if err := processImp(&imp, request); err == nil { validImps = append(validImps, imp) } else { errs = append(errs, err) @@ -120,7 +144,8 @@ func (a *TripleliftNativeAdapter) MakeRequests(request *openrtb2.BidRequest, ext Method: "POST", Uri: ad, Body: reqJSON, - Headers: headers}) + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(tlRequest.Imp)}) return reqs, errs } @@ -154,7 +179,7 @@ func (a *TripleliftNativeAdapter) MakeBids(internalRequest *openrtb2.BidRequest, return nil, []error{&errortypes.BadServerResponse{Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)}} } var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } var errs []error @@ -201,7 +226,7 @@ func getExtraInfo(v string) (TripleliftNativeExtInfo, error) { } var extraInfo TripleliftNativeExtInfo - if err := json.Unmarshal([]byte(v), &extraInfo); err != nil { + if err := jsonutil.Unmarshal([]byte(v), &extraInfo); err != nil { return extraInfo, fmt.Errorf("invalid extra info: %v", err) } diff --git a/adapters/triplelift_native/triplelift_native_test.go b/adapters/triplelift_native/triplelift_native_test.go index 18e157a41cd..651890b01e0 100644 --- a/adapters/triplelift_native/triplelift_native_test.go +++ b/adapters/triplelift_native/triplelift_native_test.go @@ -3,9 +3,9 @@ package triplelift_native import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/triplelift_native/triplelift_nativetest/exemplary/app-msn-no-tag-code.json b/adapters/triplelift_native/triplelift_nativetest/exemplary/app-msn-no-tag-code.json new file mode 100644 index 00000000000..6950942f19d --- /dev/null +++ b/adapters/triplelift_native/triplelift_nativetest/exemplary/app-msn-no-tag-code.json @@ -0,0 +1,112 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "publisher": { + "id": "foo", + "name": "foo", + "domain": "msn.com" + } + }, + "imp": [{ + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "id": "test-imp-id", + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "" + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://tlx.3lift.net/s2sn/auction?supplier_id=20", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "tagid": "invcode", + "bidfloor": 20, + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "" + } + } + }], + "app": { + "publisher": { + "id": "foo", + "name": "foo", + "domain": "msn.com" + } + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "triplelift.com" + ], + "iurl": "http://example.com", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": "aaa" + }] + }], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "id": "test-request-id", + "type": "native", + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "triplelift.com" + ], + "iurl": "http://example.com", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": "aaa" + }, + "bidid": "5778926625248726496", + "cur": "USD" + }] + }] +} \ No newline at end of file diff --git a/adapters/triplelift_native/triplelift_nativetest/exemplary/app-msn.json b/adapters/triplelift_native/triplelift_nativetest/exemplary/app-msn.json new file mode 100644 index 00000000000..420cab5aff0 --- /dev/null +++ b/adapters/triplelift_native/triplelift_nativetest/exemplary/app-msn.json @@ -0,0 +1,112 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "publisher": { + "id": "foo", + "name": "foo", + "domain": "msn.com" + } + }, + "imp": [{ + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "id": "test-imp-id", + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "bar" + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://tlx.3lift.net/s2sn/auction?supplier_id=20", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "tagid": "bar", + "bidfloor": 20, + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "bar" + } + } + }], + "app": { + "publisher": { + "id": "foo", + "name": "foo", + "domain": "msn.com" + } + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "triplelift.com" + ], + "iurl": "http://example.com", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": "aaa" + }] + }], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "id": "test-request-id", + "type": "native", + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "triplelift.com" + ], + "iurl": "http://example.com", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": "aaa" + }, + "bidid": "5778926625248726496", + "cur": "USD" + }] + }] +} \ No newline at end of file diff --git a/adapters/triplelift_native/triplelift_nativetest/exemplary/app.json b/adapters/triplelift_native/triplelift_nativetest/exemplary/app.json new file mode 100644 index 00000000000..d1cd72947b7 --- /dev/null +++ b/adapters/triplelift_native/triplelift_nativetest/exemplary/app.json @@ -0,0 +1,112 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "publisher": { + "id": "foo", + "name": "foo", + "domain": "foo.com" + } + }, + "imp": [{ + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "id": "test-imp-id", + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "bar" + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://tlx.3lift.net/s2sn/auction?supplier_id=20", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "tagid": "invcode", + "bidfloor": 20, + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "bar" + } + } + }], + "app": { + "publisher": { + "id": "foo", + "name": "foo", + "domain": "foo.com" + } + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "triplelift.com" + ], + "iurl": "http://example.com", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": "aaa" + }] + }], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "id": "test-request-id", + "type": "native", + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "triplelift.com" + ], + "iurl": "http://example.com", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": "aaa" + }, + "bidid": "5778926625248726496", + "cur": "USD" + }] + }] +} \ No newline at end of file diff --git a/adapters/triplelift_native/triplelift_nativetest/exemplary/optional-params.json b/adapters/triplelift_native/triplelift_nativetest/exemplary/optional-params.json deleted file mode 100644 index e4c90650b20..00000000000 --- a/adapters/triplelift_native/triplelift_nativetest/exemplary/optional-params.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { "publisher": {"id":"foo","name":"foo"}}, - "imp": [ - { - "native":{ - "request" : "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" - }, - "id": "test-imp-id", - "ext": { - "bidder": { - "inventoryCode": "foo", - "floor" : 20 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://tlx.3lift.net/s2sn/auction?supplier_id=20", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "native": { - "request" : "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" - }, - "tagid": "foo", - "bidfloor": 20, - "ext": { - "bidder": { - "inventoryCode": "foo", - "floor" : 20 - } - } - } - ], - "site": { - "publisher": { - "id": "foo", - "name": "foo" - } - } - } - }, - "mockResponse": { - "status": 204 - } - } - ], - "expectedBidResponses": [] -} diff --git a/adapters/triplelift_native/triplelift_nativetest/exemplary/site-msn-no-tag-code.json b/adapters/triplelift_native/triplelift_nativetest/exemplary/site-msn-no-tag-code.json new file mode 100644 index 00000000000..48162742a17 --- /dev/null +++ b/adapters/triplelift_native/triplelift_nativetest/exemplary/site-msn-no-tag-code.json @@ -0,0 +1,112 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "publisher": { + "id": "foo", + "name": "foo", + "domain": "msn.com" + } + }, + "imp": [{ + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "id": "test-imp-id", + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "" + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://tlx.3lift.net/s2sn/auction?supplier_id=20", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "tagid": "invcode", + "bidfloor": 20, + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "" + } + } + }], + "site": { + "publisher": { + "id": "foo", + "name": "foo", + "domain": "msn.com" + } + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "triplelift.com" + ], + "iurl": "http://example.com", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": "aaa" + }] + }], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "id": "test-request-id", + "type": "native", + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "triplelift.com" + ], + "iurl": "http://example.com", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": "aaa" + }, + "bidid": "5778926625248726496", + "cur": "USD" + }] + }] +} \ No newline at end of file diff --git a/adapters/triplelift_native/triplelift_nativetest/exemplary/site-msn.json b/adapters/triplelift_native/triplelift_nativetest/exemplary/site-msn.json new file mode 100644 index 00000000000..5aaa5a1ddc2 --- /dev/null +++ b/adapters/triplelift_native/triplelift_nativetest/exemplary/site-msn.json @@ -0,0 +1,112 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "publisher": { + "id": "foo", + "name": "foo", + "domain": "msn.com" + } + }, + "imp": [{ + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "id": "test-imp-id", + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "bar" + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://tlx.3lift.net/s2sn/auction?supplier_id=20", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "tagid": "bar", + "bidfloor": 20, + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "bar" + } + } + }], + "site": { + "publisher": { + "id": "foo", + "name": "foo", + "domain": "msn.com" + } + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "triplelift.com" + ], + "iurl": "http://example.com", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": "aaa" + }] + }], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "id": "test-request-id", + "type": "native", + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "triplelift.com" + ], + "iurl": "http://example.com", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": "aaa" + }, + "bidid": "5778926625248726496", + "cur": "USD" + }] + }] +} \ No newline at end of file diff --git a/adapters/triplelift_native/triplelift_nativetest/exemplary/site.json b/adapters/triplelift_native/triplelift_nativetest/exemplary/site.json new file mode 100644 index 00000000000..d273f4d411d --- /dev/null +++ b/adapters/triplelift_native/triplelift_nativetest/exemplary/site.json @@ -0,0 +1,112 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "publisher": { + "id": "foo", + "name": "foo", + "domain": "foo.com" + } + }, + "imp": [{ + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "id": "test-imp-id", + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "bar" + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://tlx.3lift.net/s2sn/auction?supplier_id=20", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "tagid": "invcode", + "bidfloor": 20, + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "bar" + } + } + }], + "site": { + "publisher": { + "id": "foo", + "name": "foo", + "domain": "foo.com" + } + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "triplelift.com" + ], + "iurl": "http://example.com", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": "aaa" + }] + }], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "id": "test-request-id", + "type": "native", + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "triplelift.com" + ], + "iurl": "http://example.com", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": "aaa" + }, + "bidid": "5778926625248726496", + "cur": "USD" + }] + }] +} \ No newline at end of file diff --git a/adapters/triplelift_native/triplelift_nativetest/supplemental/app-no-publisher.json b/adapters/triplelift_native/triplelift_nativetest/supplemental/app-no-publisher.json new file mode 100644 index 00000000000..56b339a1d79 --- /dev/null +++ b/adapters/triplelift_native/triplelift_nativetest/supplemental/app-no-publisher.json @@ -0,0 +1,25 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": {}, + "imp": [{ + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "id": "test-imp-id", + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "bar" + } + } + }] + }, + "expectedMakeRequestsErrors": [{ + "value": "Unsupported publisher for triplelift_native", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/triplelift_native/triplelift_nativetest/supplemental/app-publisher-no-domain.json b/adapters/triplelift_native/triplelift_nativetest/supplemental/app-publisher-no-domain.json new file mode 100644 index 00000000000..ac119354c36 --- /dev/null +++ b/adapters/triplelift_native/triplelift_nativetest/supplemental/app-publisher-no-domain.json @@ -0,0 +1,60 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "publisher": { + "id": "foo" + } + }, + "imp": [{ + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "id": "test-imp-id", + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "bar" + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://tlx.3lift.net/s2sn/auction?supplier_id=20", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "tagid": "invcode", + "bidfloor": 20, + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "bar" + } + } + }], + "app": { + "publisher": { + "id": "foo" + } + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 204 + } + }], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/triplelift_native/triplelift_nativetest/supplemental/badext.json b/adapters/triplelift_native/triplelift_nativetest/supplemental/badext.json index 7df178bdd00..eb2d6a1e19d 100644 --- a/adapters/triplelift_native/triplelift_nativetest/supplemental/badext.json +++ b/adapters/triplelift_native/triplelift_nativetest/supplemental/badext.json @@ -1,7 +1,7 @@ { "expectedMakeRequestsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "value": "expect { or n, but found \"", "comparison": "literal" }, { diff --git a/adapters/triplelift_native/triplelift_nativetest/supplemental/badextbidder.json b/adapters/triplelift_native/triplelift_nativetest/supplemental/badextbidder.json index 59ebe048b19..deb9dfce308 100644 --- a/adapters/triplelift_native/triplelift_nativetest/supplemental/badextbidder.json +++ b/adapters/triplelift_native/triplelift_nativetest/supplemental/badextbidder.json @@ -1,7 +1,7 @@ { "expectedMakeRequestsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpTriplelift", + "value": "expect { or n, but found \"", "comparison": "literal" }, { diff --git a/adapters/triplelift_native/triplelift_nativetest/supplemental/badresponseext.json b/adapters/triplelift_native/triplelift_nativetest/supplemental/badresponseext.json index c9bb90f8a02..86884d37eaa 100644 --- a/adapters/triplelift_native/triplelift_nativetest/supplemental/badresponseext.json +++ b/adapters/triplelift_native/triplelift_nativetest/supplemental/badresponseext.json @@ -37,7 +37,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/triplelift_native/triplelift_nativetest/supplemental/badstatuscode.json b/adapters/triplelift_native/triplelift_nativetest/supplemental/badstatuscode.json index 175ec038162..7b4cf0cdf19 100644 --- a/adapters/triplelift_native/triplelift_nativetest/supplemental/badstatuscode.json +++ b/adapters/triplelift_native/triplelift_nativetest/supplemental/badstatuscode.json @@ -37,7 +37,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400 diff --git a/adapters/triplelift_native/triplelift_nativetest/supplemental/effective-publisher-allowed.json b/adapters/triplelift_native/triplelift_nativetest/supplemental/effective-publisher-allowed.json new file mode 100644 index 00000000000..74ac9294783 --- /dev/null +++ b/adapters/triplelift_native/triplelift_nativetest/supplemental/effective-publisher-allowed.json @@ -0,0 +1,122 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "publisher": { + "id": "baz", + "name": "foo", + "domain": "foo.com", + "ext": { + "prebid": { + "parentAccount": "foo" + } + } + } + }, + "imp": [{ + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "id": "test-imp-id", + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "bar" + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://tlx.3lift.net/s2sn/auction?supplier_id=20", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "tagid": "invcode", + "bidfloor": 20, + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "bar" + } + } + }], + "site": { + "publisher": { + "id": "baz", + "name": "foo", + "domain": "foo.com", + "ext": { + "prebid": { + "parentAccount": "foo" + } + } + } + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "triplelift.com" + ], + "iurl": "http://example.com", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": "aaa" + }] + }], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "id": "test-request-id", + "type": "native", + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "triplelift.com" + ], + "iurl": "http://example.com", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": "aaa" + }, + "bidid": "5778926625248726496", + "cur": "USD" + }] + }] +} \ No newline at end of file diff --git a/adapters/triplelift_native/triplelift_nativetest/supplemental/effective-publisher-not-allowed.json b/adapters/triplelift_native/triplelift_nativetest/supplemental/effective-publisher-not-allowed.json new file mode 100644 index 00000000000..d43c18cc402 --- /dev/null +++ b/adapters/triplelift_native/triplelift_nativetest/supplemental/effective-publisher-not-allowed.json @@ -0,0 +1,36 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "publisher": { + "id": "baz", + "name": "foo", + "domain": "foo.com", + "ext": { + "prebid": { + "parentAccount": "faz" + } + } + } + }, + "imp": [{ + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "id": "test-imp-id", + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "bar" + } + } + }] + }, + "expectedMakeRequestsErrors": [{ + "value": "Unsupported publisher for triplelift_native", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/triplelift_native/triplelift_nativetest/supplemental/no-imp-ext-data.json b/adapters/triplelift_native/triplelift_nativetest/supplemental/no-imp-ext-data.json new file mode 100644 index 00000000000..a46d0483fea --- /dev/null +++ b/adapters/triplelift_native/triplelift_nativetest/supplemental/no-imp-ext-data.json @@ -0,0 +1,85 @@ +{ + "mockBidRequest": { + "site": { + "publisher": { + "id": "foo", + "name": "foo" + } + }, + "id": "test-request-id", + "imp": [{ + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "id": "test-imp-id", + "ext": { + "bidder": { + "inventoryCode": "invcode" + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://tlx.3lift.net/s2sn/auction?supplier_id=20", + "body": { + "site": { + "publisher": { + "id": "foo", + "name": "foo" + } + }, + "id": "test-request-id", + "imp": [{ + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "id": "test-imp-id", + "tagid": "invcode", + "ext": { + "bidder": { + "inventoryCode": "invcode" + } + } + }] + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 302, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "triplelift.com" + ], + "iurl": "http://nym1-ib.adnxs.com/cr?id=29681110", + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "ext": { + "triplelift_pb": { + "format": 2 + } + } + }] + }], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [{ + "value": "Unexpected status code: 302. Run with request.debug = 1 for more info", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/triplelift_native/triplelift_nativetest/supplemental/notgoodstatuscode.json b/adapters/triplelift_native/triplelift_nativetest/supplemental/notgoodstatuscode.json deleted file mode 100644 index ce40d36f0ff..00000000000 --- a/adapters/triplelift_native/triplelift_nativetest/supplemental/notgoodstatuscode.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "mockBidRequest": { - "site": { "publisher": {"id":"foo","name":"foo"}}, - "id": "test-request-id", - "imp": [ - { - "native":{ - "request" : "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" - }, - "id": "test-imp-id", - "ext": { - "bidder": { - "inventoryCode": "aa" - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://tlx.3lift.net/s2sn/auction?supplier_id=20", - "body": { - "site": { "publisher": {"id":"foo","name":"foo"}}, - "id": "test-request-id", - "imp": [ - { - "native":{ - "request" : "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" - }, - "id": "test-imp-id", - "tagid" : "aa", - "ext": { - "bidder": { - "inventoryCode": "aa" - } - } - } - ] - } - }, - "mockResponse": { - "status": 302, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "958", - "bid": [ - { - "id": "7706636740145184841", - "impid": "test-imp-id", - "price": 0.5, - "adid": "29681110", - "adm": "some-test-ad", - "adomain": [ - "triplelift.com" - ], - "iurl": "http://nym1-ib.adnxs.com/cr?id=29681110", - "cid": "958", - "crid": "29681110", - "h": 250, - "w": 300, - "ext": { - "triplelift_pb": { - "format": 2 - } - } - } - ] - } - ], - "bidid": "5778926625248726496", - "cur": "USD" - } - } - } - ], - "expectedBidResponses": [], - "expectedMakeBidsErrors": [ - { - "value": "Unexpected status code: 302. Run with request.debug = 1 for more info", - "comparison": "literal" - } - ] -} diff --git a/adapters/triplelift_native/triplelift_nativetest/supplemental/site-no-publisher.json b/adapters/triplelift_native/triplelift_nativetest/supplemental/site-no-publisher.json new file mode 100644 index 00000000000..62f9bdab17c --- /dev/null +++ b/adapters/triplelift_native/triplelift_nativetest/supplemental/site-no-publisher.json @@ -0,0 +1,25 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": {}, + "imp": [{ + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "id": "test-imp-id", + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "bar" + } + } + }] + }, + "expectedMakeRequestsErrors": [{ + "value": "Unsupported publisher for triplelift_native", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/triplelift_native/triplelift_nativetest/supplemental/site-publisher-no-domain.json b/adapters/triplelift_native/triplelift_nativetest/supplemental/site-publisher-no-domain.json new file mode 100644 index 00000000000..d44ae442fab --- /dev/null +++ b/adapters/triplelift_native/triplelift_nativetest/supplemental/site-publisher-no-domain.json @@ -0,0 +1,62 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "publisher": { + "id": "foo", + "name": "foo" + } + }, + "imp": [{ + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "id": "test-imp-id", + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "bar" + } + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://tlx.3lift.net/s2sn/auction?supplier_id=20", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "{\"plcmtcnt\":1,\"plcmttype\":2,\"privacy\":1,\"context\":1,\"contextsubtype\":12,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"assets\":[{\"data\":{\"type\":12},\"required\":1},{\"title\":{\"len\":50},\"required\":1},{\"img\":{\"w\":80,\"h\":80,\"type\":1},\"required\":1},{\"img\":{\"w\":1200,\"h\":627,\"type\":3},\"required\":1},{\"data\":{\"type\":3},\"required\":0},{\"data\":{\"len\":100,\"type\":2},\"required\":1},{\"video\":{\"mimes\":[\"video/mpeg\",\"video/mp4\"],\"minduration\":2,\"protocols\":[2,5],\"maxduration\":2,\"ext\":{\"playbackmethod\":[1,2]}},\"required\":1}],\"ver\":\"1.2\"}" + }, + "tagid": "invcode", + "bidfloor": 20, + "ext": { + "bidder": { + "inventoryCode": "invcode", + "floor": 20 + }, + "data": { + "tag_code": "bar" + } + } + }], + "site": { + "publisher": { + "id": "foo", + "name": "foo" + } + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 204 + } + }], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/trustedstack/params_test.go b/adapters/trustedstack/params_test.go new file mode 100644 index 00000000000..a38e0bb7227 --- /dev/null +++ b/adapters/trustedstack/params_test.go @@ -0,0 +1,54 @@ +package trustedstack + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/trustedstack.json +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderTrustedstack, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected trustedstack params: %s", validParam) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderTrustedstack, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"cid":"123", "crid":"1234"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"cid":"", "crid":""}`, + `{"cid":"only cid is present"}`, + `{"crid":"only crid is present"}`, + `{"ccid":"123","ccrid":"123"}`, + `{"aid":123, "placementId":"123", "siteId":"321"}`, +} diff --git a/adapters/trustedstack/trustedstack.go b/adapters/trustedstack/trustedstack.go new file mode 100644 index 00000000000..7c2fd4df50a --- /dev/null +++ b/adapters/trustedstack/trustedstack.go @@ -0,0 +1,109 @@ +package trustedstack + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +type adapter struct { + endpoint string +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + + reqJson, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.endpoint, + Body: reqJson, + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), + }}, nil +} + +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var errs []error + + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil { + return nil, []error{err} + } + + var bidResp openrtb2.BidResponse + + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponse() + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + bidType, err := getBidMediaTypeFromMtype(&sb.Bid[i]) + if err != nil { + errs = append(errs, err) + } else { + b := &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + } + return bidResponse, errs +} + +// Builder builds a new instance of the Trustedstack adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + url := buildEndpoint(config.Endpoint, server.ExternalUrl) + return &adapter{ + endpoint: url, + }, nil +} + +func getBidMediaTypeFromMtype(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return "", fmt.Errorf("unable to fetch mediaType for imp: %s", bid.ImpID) + } +} + +func buildEndpoint(trustedstackUrl, hostUrl string) string { + + if len(hostUrl) == 0 { + return trustedstackUrl + } + urlObject, err := url.Parse(trustedstackUrl) + if err != nil { + return trustedstackUrl + } + values := urlObject.Query() + values.Add("src", hostUrl) + urlObject.RawQuery = values.Encode() + return urlObject.String() +} diff --git a/adapters/trustedstack/trustedstack_test.go b/adapters/trustedstack/trustedstack_test.go new file mode 100644 index 00000000000..d12202802e0 --- /dev/null +++ b/adapters/trustedstack/trustedstack_test.go @@ -0,0 +1,31 @@ +package trustedstack + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderTrustedstack, config.Adapter{ + Endpoint: "https://example.trustedstack.com/rtb/prebid", + ExtraAdapterInfo: "http://localhost:8080/extrnal_url", + }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "trustedstacktest", bidder) +} + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderTrustedstack, config.Adapter{ + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + assert.Nil(t, buildErr) +} diff --git a/adapters/trustedstack/trustedstacktest/exemplary/multi-format.json b/adapters/trustedstack/trustedstacktest/exemplary/multi-format.json new file mode 100644 index 00000000000..21d0bb1e520 --- /dev/null +++ b/adapters/trustedstack/trustedstacktest/exemplary/multi-format.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "1", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 320, + "h": 480 + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.trustedstack.com/rtb/prebid?src=http%3A%2F%2Fhosturl.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "1", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 320, + "h": 480 + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + } + ] + }, + "impIDs": ["1"] + }, + + "mockResponse": { + "status": 204, + "body": "" + } + } + ], + + "expectedBidResponses": [] +} diff --git a/adapters/trustedstack/trustedstacktest/exemplary/multi-imps.json b/adapters/trustedstack/trustedstacktest/exemplary/multi-imps.json new file mode 100644 index 00000000000..dc22967dce8 --- /dev/null +++ b/adapters/trustedstack/trustedstacktest/exemplary/multi-imps.json @@ -0,0 +1,135 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "1", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + }, + { + "id": "2", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "cid": "8CUTSESHA", + "crid": "9999999789" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.trustedstack.com/rtb/prebid?src=http%3A%2F%2Fhosturl.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "1", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + }, + { + "id": "2", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "cid": "8CUTSESHA", + "crid": "9999999789" + } + } + } + ] + }, + "impIDs": ["1","2"] + }, + + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "trustedstack", + "bid": [ + { + "id": "test-bid-id", + "impid": "1", + "price": 1.50, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 50, + "w": 320, + "mtype": 1 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "1", + "price": 1.50, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 320, + "h": 50, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/suntContent/suntContenttest/supplemental/status_no_content.json b/adapters/trustedstack/trustedstacktest/exemplary/no-bid.json similarity index 61% rename from adapters/suntContent/suntContenttest/supplemental/status_no_content.json rename to adapters/trustedstack/trustedstacktest/exemplary/no-bid.json index 8eda774c657..195064c807f 100644 --- a/adapters/suntContent/suntContenttest/supplemental/status_no_content.json +++ b/adapters/trustedstack/trustedstacktest/exemplary/no-bid.json @@ -3,64 +3,59 @@ "id": "test-request-id", "imp": [ { - "id": "test-imp-id", + "id": "1", "banner": { "format": [ { "w": 300, "h": 250 - }, - { - "w": 300, - "h": 600 } ] }, "ext": { "bidder": { - "adUnitId": "example-tag-id" + "cid": "8CUTSTCID", + "crid": "999999999" } } } ] }, + "httpCalls": [ { "expectedRequest": { - "uri": "https://mockup.suntcontent.com/", + "uri": "https://example.trustedstack.com/rtb/prebid?src=http%3A%2F%2Fhosturl.com", "body": { - "cur": [ - "EUR" - ], "id": "test-request-id", "imp": [ { - "id": "test-imp-id", + "id": "1", "banner": { "format": [ { "w": 300, "h": 250 - }, - { - "w": 300, - "h": 600 } ] }, - "tagid": "example-tag-id", "ext": { "bidder": { - "adUnitId": "example-tag-id" + "cid": "8CUTSTCID", + "crid": "999999999" } } } ] - } + }, + "impIDs":["1"] }, "mockResponse": { - "status": 204 + "status": 204, + "body": {} } } - ] -} \ No newline at end of file + ], + + "expectedBidResponses": [] +} diff --git a/adapters/suntContent/suntContenttest/supplemental/status_not_ok.json b/adapters/trustedstack/trustedstacktest/exemplary/optional-params.json similarity index 56% rename from adapters/suntContent/suntContenttest/supplemental/status_not_ok.json rename to adapters/trustedstack/trustedstacktest/exemplary/optional-params.json index 07618aed7fa..105b5d2981a 100644 --- a/adapters/suntContent/suntContenttest/supplemental/status_not_ok.json +++ b/adapters/trustedstack/trustedstacktest/exemplary/optional-params.json @@ -3,70 +3,61 @@ "id": "test-request-id", "imp": [ { - "id": "test-imp-id", + "id": "1", "banner": { "format": [ { "w": 300, "h": 250 - }, - { - "w": 300, - "h": 600 } ] }, "ext": { "bidder": { - "adUnitId": "example-tag-id" + "cid": "8CUTSTCID", + "crid": "999999999", + "tagid_src": "ext" } } } ] }, + "httpCalls": [ { "expectedRequest": { - "uri": "https://mockup.suntcontent.com/", + "uri": "https://example.trustedstack.com/rtb/prebid?src=http%3A%2F%2Fhosturl.com", "body": { - "cur": [ - "EUR" - ], "id": "test-request-id", "imp": [ { - "id": "test-imp-id", + "id": "1", "banner": { "format": [ { "w": 300, "h": 250 - }, - { - "w": 300, - "h": 600 } ] }, - "tagid": "example-tag-id", "ext": { "bidder": { - "adUnitId": "example-tag-id" + "cid": "8CUTSTCID", + "crid": "999999999", + "tagid_src": "ext" } } } ] - } + }, + "impIDs":["1"] }, "mockResponse": { - "status": 404 + "status": 204, + "body": {} } } ], - "expectedMakeBidsErrors": [ - { - "value": "Unexpected status code: 404. Run with request.debug = 1 for more info.", - "comparison": "literal" - } - ] -} \ No newline at end of file + + "expectedBidResponses": [] +} diff --git a/adapters/trustedstack/trustedstacktest/exemplary/simple-banner.json b/adapters/trustedstack/trustedstacktest/exemplary/simple-banner.json new file mode 100644 index 00000000000..c0fb7519a33 --- /dev/null +++ b/adapters/trustedstack/trustedstacktest/exemplary/simple-banner.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "1", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.trustedstack.com/rtb/prebid?src=http%3A%2F%2Fhosturl.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "1", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + } + ] + }, + "impIDs":["1"] + }, + + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "trustedstack", + "bid": [ + { + "id": "test-bid-id", + "impid": "1", + "price": 1.50, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 50, + "w": 320, + "mtype": 1 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "1", + "price": 1.50, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 320, + "h": 50, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/trustedstack/trustedstacktest/exemplary/simple-native.json b/adapters/trustedstack/trustedstacktest/exemplary/simple-native.json new file mode 100644 index 00000000000..7af9ce8eba7 --- /dev/null +++ b/adapters/trustedstack/trustedstacktest/exemplary/simple-native.json @@ -0,0 +1,91 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "1", + "native": { + "request": "sample-native-request" + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.trustedstack.com/rtb/prebid?src=http%3A%2F%2Fhosturl.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "1", + "native": { + "request": "sample-native-request" + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + } + ] + }, + "impIDs":["1"] + }, + + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "trustedstack", + "bid": [ + { + "id": "test-bid-id", + "impid": "1", + "price": 1.50, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 50, + "w": 320, + "mtype": 4 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "1", + "price": 1.50, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 320, + "h": 50, + "mtype": 4 + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/trustedstack/trustedstacktest/exemplary/simple-video.json b/adapters/trustedstack/trustedstacktest/exemplary/simple-video.json new file mode 100644 index 00000000000..65357342234 --- /dev/null +++ b/adapters/trustedstack/trustedstacktest/exemplary/simple-video.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "1", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 320, + "h": 480 + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.trustedstack.com/rtb/prebid?src=http%3A%2F%2Fhosturl.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "1", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 320, + "h": 480 + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + } + ] + }, + "impIDs":["1"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "trustedstack", + "bid": [ + { + "id": "test-bid-id", + "impid": "1", + "price": 2.50, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 320, + "h": 480, + "mtype": 2 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "1", + "price": 2.50, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 320, + "h": 480, + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/trustedstack/trustedstacktest/supplemental/invalid-req-400-status-code-bad-request.json b/adapters/trustedstack/trustedstacktest/supplemental/invalid-req-400-status-code-bad-request.json new file mode 100644 index 00000000000..7c2cb7c632b --- /dev/null +++ b/adapters/trustedstack/trustedstacktest/supplemental/invalid-req-400-status-code-bad-request.json @@ -0,0 +1,97 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://example.trustedstack.com/rtb/prebid?src=http%3A%2F%2Fhosturl.com", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + } + ], + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "tmax": 1000 + }, + "impIDs": ["some-impression-id"] + }, + "mockResponse": { + "status": 400 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/trustedstack/trustedstacktest/supplemental/invalid-req-500-status-code-bad-request.json b/adapters/trustedstack/trustedstacktest/supplemental/invalid-req-500-status-code-bad-request.json new file mode 100644 index 00000000000..6d57a29487e --- /dev/null +++ b/adapters/trustedstack/trustedstacktest/supplemental/invalid-req-500-status-code-bad-request.json @@ -0,0 +1,98 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://example.trustedstack.com/rtb/prebid?src=http%3A%2F%2Fhosturl.com", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + } + ], + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "tmax": 1000 + }, + "impIDs": ["some-impression-id"] + }, + "mockResponse": { + "status": 500 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/trustedstack/trustedstacktest/supplemental/ivalid-req-200-incorrect-response-mtype.json b/adapters/trustedstack/trustedstacktest/supplemental/ivalid-req-200-incorrect-response-mtype.json new file mode 100644 index 00000000000..0b1dfbb5c66 --- /dev/null +++ b/adapters/trustedstack/trustedstacktest/supplemental/ivalid-req-200-incorrect-response-mtype.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "userAgent", + "ip": "193.168.244.1" + }, + "at": 1, + "tmax": 5000, + "cur": [ + "USD" + ], + "regs": { + "gdpr": 0 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://example.trustedstack.com/rtb/prebid?src=http%3A%2F%2Fhosturl.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "userAgent", + "ip": "193.168.244.1" + }, + "at": 1, + "tmax": 5000, + "cur": [ + "USD" + ], + "regs": { + "gdpr": 0 + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "tid", + "seatbid": [ + { + "seat": "trustedstack", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 5 + } + ] + } + ], + "bidid": "bid01" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [] + }], + "expectedMakeBidsErrors": [ + { + "value": "unable to fetch mediaType for imp: test-imp-id", + "comparison": "literal" + } + ] +} diff --git a/adapters/trustedstack/trustedstacktest/supplemental/valid-req-200-bid-response-from-trustedstack.json b/adapters/trustedstack/trustedstacktest/supplemental/valid-req-200-bid-response-from-trustedstack.json new file mode 100644 index 00000000000..a2388abea37 --- /dev/null +++ b/adapters/trustedstack/trustedstacktest/supplemental/valid-req-200-bid-response-from-trustedstack.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "userAgent", + "ip": "193.168.244.1" + }, + "at": 1, + "tmax": 5000, + "cur": [ + "USD" + ], + "regs": { + "gdpr": 0 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://example.trustedstack.com/rtb/prebid?src=http%3A%2F%2Fhosturl.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "userAgent", + "ip": "193.168.244.1" + }, + "at": 1, + "tmax": 5000, + "cur": [ + "USD" + ], + "regs": { + "gdpr": 0 + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "tid", + "seatbid": [ + { + "seat": "trustedstack", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + } + ] + } + ], + "bidid": "bid01" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300, + "mtype": 1 + }, + "type": "banner" + }] + }] +} diff --git a/adapters/trustedstack/trustedstacktest/supplemental/valid-req-200-incorrect-response-format.json b/adapters/trustedstack/trustedstacktest/supplemental/valid-req-200-incorrect-response-format.json new file mode 100644 index 00000000000..2852d8a2443 --- /dev/null +++ b/adapters/trustedstack/trustedstacktest/supplemental/valid-req-200-incorrect-response-format.json @@ -0,0 +1,121 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "userAgent", + "ip": "193.168.244.1" + }, + "at": 1, + "tmax": 5000, + "cur": [ + "USD" + ], + "regs": { + "gdpr": 0 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://example.trustedstack.com/rtb/prebid?src=http%3A%2F%2Fhosturl.com", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com", + "publisher": { + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "userAgent", + "ip": "193.168.244.1" + }, + "at": 1, + "tmax": 5000, + "cur": [ + "USD" + ], + "regs": { + "gdpr": 0 + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "tid", + "seatbid": [ + { + "seat": "trustedstack", + "bid": [ + { + "id": "randomid", + "impid": "test-imp-id", + "price": "0.500000", + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300 + } + ] + } + ], + "bidid": "bid01" + } + } + }], + + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "cannot unmarshal openrtb2.Bid.Price: invalid number", + "comparison": "literal" + } + ] +} diff --git a/adapters/trustedstack/trustedstacktest/supplemental/valid-req-204-response-from-trustedstack.json b/adapters/trustedstack/trustedstacktest/supplemental/valid-req-204-response-from-trustedstack.json new file mode 100755 index 00000000000..39e95277213 --- /dev/null +++ b/adapters/trustedstack/trustedstacktest/supplemental/valid-req-204-response-from-trustedstack.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + }, + "banner": { + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.trustedstack.com/rtb/prebid?src=http%3A%2F%2Fhosturl.com", + "body": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "cid": "8CUTSTCID", + "crid": "999999999" + } + }, + "banner": { + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + }, + "impIDs": ["imp-id"] + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/ucfunnel/params_test.go b/adapters/ucfunnel/params_test.go index b721925e72a..47954e8aadb 100644 --- a/adapters/ucfunnel/params_test.go +++ b/adapters/ucfunnel/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // This file actually intends to test static/bidder-params/ucfunnel.json diff --git a/adapters/ucfunnel/ucfunnel.go b/adapters/ucfunnel/ucfunnel.go index a0d86a0fa29..efee7736d42 100644 --- a/adapters/ucfunnel/ucfunnel.go +++ b/adapters/ucfunnel/ucfunnel.go @@ -6,11 +6,12 @@ import ( "net/http" "net/url" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type UcfunnelAdapter struct { @@ -44,12 +45,12 @@ func (a *UcfunnelAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa var errs []error var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } var bidReq openrtb2.BidRequest - if err := json.Unmarshal(externalRequest.Body, &bidReq); err != nil { + if err := jsonutil.Unmarshal(externalRequest.Body, &bidReq); err != nil { return nil, []error{err} } @@ -75,7 +76,7 @@ func (a *UcfunnelAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad // If all the requests were malformed, don't bother making a server call with no impressions. if len(request.Imp) == 0 { return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("No impression in the bid request\n"), + Message: "No impression in the bid request\n", }} } @@ -98,13 +99,14 @@ func (a *UcfunnelAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad Uri: uri, Body: reqJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }}, errs } func getPartnerId(request *openrtb2.BidRequest) (string, []error) { var ext ExtBidderUcfunnel var errs = []error{} - err := json.Unmarshal(request.Imp[0].Ext, &ext) + err := jsonutil.Unmarshal(request.Imp[0].Ext, &ext) if err != nil { errs = append(errs, err) return "", errs diff --git a/adapters/ucfunnel/ucfunnel_test.go b/adapters/ucfunnel/ucfunnel_test.go index a906b9279e8..ec1b777c79b 100644 --- a/adapters/ucfunnel/ucfunnel_test.go +++ b/adapters/ucfunnel/ucfunnel_test.go @@ -5,10 +5,11 @@ import ( "fmt" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/stretchr/testify/assert" ) func TestMakeRequests(t *testing.T) { @@ -54,23 +55,43 @@ func TestMakeRequests(t *testing.T) { } var testCases = []struct { - in []openrtb2.BidRequest - out1 [](int) - out2 [](bool) + giveRequest openrtb2.BidRequest + wantErr bool + wantRequest bool + wantImpIDs []string }{ { - in: []openrtb2.BidRequest{internalRequest01, internalRequest02, internalRequest03}, - out1: [](int){1, 1, 0}, - out2: [](bool){false, false, true}, + giveRequest: internalRequest01, + wantErr: true, + wantRequest: false, + wantImpIDs: []string{}, + }, + { + giveRequest: internalRequest02, + wantErr: true, + wantRequest: false, + wantImpIDs: []string{imp.ID, imp2.ID, imp3.ID, imp4.ID, imp5.ID}, + }, + { + giveRequest: internalRequest03, + wantErr: false, + wantRequest: true, + wantImpIDs: []string{imp.ID, imp2.ID, imp3.ID, imp4.ID, imp5.ID}, }, } - for idx := range testCases { - for i := range testCases[idx].in { - RequestData, err := bidder.MakeRequests(&testCases[idx].in[i], nil) - if ((RequestData == nil) == testCases[idx].out2[i]) && (len(err) == testCases[idx].out1[i]) { - t.Errorf("actual = %v expected = %v", len(err), testCases[idx].out1[i]) - } + for _, tc := range testCases { + RequestData, err := bidder.MakeRequests(&tc.giveRequest, nil) + if tc.wantErr { + assert.Len(t, err, 1) + } else { + assert.Len(t, err, 0) + } + if tc.wantRequest { + assert.Len(t, RequestData, 1) + assert.ElementsMatch(t, tc.wantImpIDs, RequestData[0].ImpIDs) + } else { + assert.Len(t, RequestData, 0) } } } diff --git a/adapters/undertone/params_test.go b/adapters/undertone/params_test.go index b48d08188d5..dad74a08e8c 100644 --- a/adapters/undertone/params_test.go +++ b/adapters/undertone/params_test.go @@ -2,8 +2,9 @@ package undertone import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/undertone/undertone.go b/adapters/undertone/undertone.go index 7f8dde35abb..91b2527c6d3 100644 --- a/adapters/undertone/undertone.go +++ b/adapters/undertone/undertone.go @@ -6,12 +6,13 @@ import ( "net/http" "strconv" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) const adapterId = 4 @@ -62,6 +63,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte Method: "POST", Uri: a.endpoint, Body: requestJSON, + ImpIDs: openrtb_ext.GetImpIDs(reqCopy.Imp), } return []*adapters.RequestData{requestData}, errs @@ -87,7 +89,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R } var response openrtb2.BidResponse - if err := json.Unmarshal(responseData.Body, &response); err != nil { + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { return nil, []error{err} } @@ -164,7 +166,7 @@ func getImpsAndPublisherId(bidRequest *openrtb2.BidRequest) ([]openrtb2.Imp, int for _, imp := range bidRequest.Imp { var ext impExt - if err := json.Unmarshal(imp.Ext, &ext); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &ext); err != nil { errs = append(errs, getInvalidImpErr(imp.ID, err)) continue } diff --git a/adapters/undertone/undertone_test.go b/adapters/undertone/undertone_test.go index d7e6d52339b..45281a1d150 100644 --- a/adapters/undertone/undertone_test.go +++ b/adapters/undertone/undertone_test.go @@ -1,10 +1,11 @@ package undertone import ( - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/undertone/undertonetest/exemplary/multi-imp-app-request.json b/adapters/undertone/undertonetest/exemplary/multi-imp-app-request.json index 71ad2ae199d..45d4fcf2238 100644 --- a/adapters/undertone/undertonetest/exemplary/multi-imp-app-request.json +++ b/adapters/undertone/undertonetest/exemplary/multi-imp-app-request.json @@ -135,7 +135,8 @@ "id": 4, "version": "1.0.0" } - } + }, + "impIDs":["test-imp-banner-id","test-imp-video-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/undertone/undertonetest/exemplary/multi-imp-site-request.json b/adapters/undertone/undertonetest/exemplary/multi-imp-site-request.json index 9e59ffbca63..d72ef0d850b 100644 --- a/adapters/undertone/undertonetest/exemplary/multi-imp-site-request.json +++ b/adapters/undertone/undertonetest/exemplary/multi-imp-site-request.json @@ -135,7 +135,8 @@ "id": 4, "version": "1.0.0" } - } + }, + "impIDs":["test-imp-banner-id","test-imp-video-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/undertone/undertonetest/supplemental/badrequest.json b/adapters/undertone/undertonetest/supplemental/badrequest.json index 1d432fe01c0..7e11f8b2122 100644 --- a/adapters/undertone/undertonetest/supplemental/badrequest.json +++ b/adapters/undertone/undertonetest/supplemental/badrequest.json @@ -131,7 +131,8 @@ "id": 4, "version": "1.0.0" } - } + }, + "impIDs":["test-imp-banner-id","test-imp-video-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/undertone/undertonetest/supplemental/internalerror.json b/adapters/undertone/undertonetest/supplemental/internalerror.json index c4a58ef14d5..be66986e8e8 100644 --- a/adapters/undertone/undertonetest/supplemental/internalerror.json +++ b/adapters/undertone/undertonetest/supplemental/internalerror.json @@ -131,7 +131,8 @@ "id": 4, "version": "1.0.0" } - } + }, + "impIDs":["test-imp-banner-id","test-imp-video-id"] }, "mockResponse": { "status": 500, diff --git a/adapters/undertone/undertonetest/supplemental/nocontent.json b/adapters/undertone/undertonetest/supplemental/nocontent.json index 4999f16bb88..f68a5879d89 100644 --- a/adapters/undertone/undertonetest/supplemental/nocontent.json +++ b/adapters/undertone/undertonetest/supplemental/nocontent.json @@ -131,7 +131,8 @@ "id": 4, "version": "1.0.0" } - } + }, + "impIDs":["test-imp-banner-id","test-imp-video-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/unicorn/params_test.go b/adapters/unicorn/params_test.go index 9313183fbfa..89768ea8245 100644 --- a/adapters/unicorn/params_test.go +++ b/adapters/unicorn/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/unicorn/unicorn.go b/adapters/unicorn/unicorn.go index 5048d9d2394..a957cff9a1c 100644 --- a/adapters/unicorn/unicorn.go +++ b/adapters/unicorn/unicorn.go @@ -7,11 +7,12 @@ import ( "net/http" "github.com/buger/jsonparser" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { @@ -51,7 +52,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte Message: "COPPA is not supported", }} } - if err := json.Unmarshal(request.Regs.Ext, &extRegs); err == nil { + if err := jsonutil.Unmarshal(request.Regs.Ext, &extRegs); err == nil { if extRegs.GDPR != nil && (*extRegs.GDPR == 1) { return nil, []error{&errortypes.BadInput{ Message: "GDPR is not supported", @@ -98,6 +99,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte Uri: a.endpoint, Body: requestJSON, Headers: getHeaders(request), + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), } return []*adapters.RequestData{requestData}, nil @@ -131,7 +133,7 @@ func modifyImps(request *openrtb2.BidRequest) error { imp := &request.Imp[i] var ext unicornImpExt - err := json.Unmarshal(imp.Ext, &ext) + err := jsonutil.Unmarshal(imp.Ext, &ext) if err != nil { return &errortypes.BadInput{ @@ -213,7 +215,7 @@ func setExt(request *openrtb2.BidRequest) (json.RawMessage, error) { } var decodedExt *unicornExt - err = json.Unmarshal(request.Ext, &decodedExt) + err = jsonutil.Unmarshal(request.Ext, &decodedExt) if err != nil { decodedExt = &unicornExt{ Prebid: nil, @@ -252,7 +254,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R } var response openrtb2.BidResponse - if err := json.Unmarshal(responseData.Body, &response); err != nil { + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { return nil, []error{err} } diff --git a/adapters/unicorn/unicorn_test.go b/adapters/unicorn/unicorn_test.go index 084be78498a..a83951277fa 100644 --- a/adapters/unicorn/unicorn_test.go +++ b/adapters/unicorn/unicorn_test.go @@ -3,9 +3,9 @@ package unicorn import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-no-app-publisher.json b/adapters/unicorn/unicorntest/exemplary/banner-app-no-app-publisher.json index 3ddb63d0cc2..dde1c2930f3 100644 --- a/adapters/unicorn/unicorntest/exemplary/banner-app-no-app-publisher.json +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-no-app-publisher.json @@ -153,7 +153,8 @@ "user": { "gender": "O" } - } + }, + "impIDs":["29D2F33E-F865-40DA-9320-16EF77935254"] }, "mockResponse": { "url": "https://ds.uncn.jp", diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-no-mediaid.json b/adapters/unicorn/unicorntest/exemplary/banner-app-no-mediaid.json index 8e526449fc4..bc6a2f51506 100644 --- a/adapters/unicorn/unicorntest/exemplary/banner-app-no-mediaid.json +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-no-mediaid.json @@ -153,7 +153,8 @@ "user": { "gender": "O" } - } + }, + "impIDs":["29D2F33E-F865-40DA-9320-16EF77935254"] }, "mockResponse": { "url": "https://ds.uncn.jp", diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-no-publisherid.json b/adapters/unicorn/unicorntest/exemplary/banner-app-no-publisherid.json index 924c2e4026b..308c3511dbf 100644 --- a/adapters/unicorn/unicorntest/exemplary/banner-app-no-publisherid.json +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-no-publisherid.json @@ -154,7 +154,8 @@ "user": { "gender": "O" } - } + }, + "impIDs":["29D2F33E-F865-40DA-9320-16EF77935254"] }, "mockResponse": { "url": "https://ds.uncn.jp", diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-no-source.json b/adapters/unicorn/unicorntest/exemplary/banner-app-no-source.json index 938c9aa1f13..4b876c94518 100644 --- a/adapters/unicorn/unicorntest/exemplary/banner-app-no-source.json +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-no-source.json @@ -152,7 +152,8 @@ "user": { "gender": "O" } - } + }, + "impIDs":["29D2F33E-F865-40DA-9320-16EF77935254"] }, "mockResponse": { "url": "https://ds.uncn.jp", diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-with-ip.json b/adapters/unicorn/unicorntest/exemplary/banner-app-with-ip.json index 74539fce60d..85d57cc57ec 100644 --- a/adapters/unicorn/unicorntest/exemplary/banner-app-with-ip.json +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-with-ip.json @@ -160,7 +160,8 @@ "user": { "gender": "O" } - } + }, + "impIDs":["29D2F33E-F865-40DA-9320-16EF77935254"] }, "mockResponse": { "url": "https://ds.uncn.jp", diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-with-ipv6.json b/adapters/unicorn/unicorntest/exemplary/banner-app-with-ipv6.json index 8c3292447b7..d38c67d615e 100644 --- a/adapters/unicorn/unicorntest/exemplary/banner-app-with-ipv6.json +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-with-ipv6.json @@ -160,7 +160,8 @@ "user": { "gender": "O" } - } + }, + "impIDs":["29D2F33E-F865-40DA-9320-16EF77935254"] }, "mockResponse": { "url": "https://ds.uncn.jp", diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json b/adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json index bc8ec8a6d6e..44fb55971b4 100644 --- a/adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json @@ -138,7 +138,8 @@ "user": { "gender": "O" } - } + }, + "impIDs":["29D2F33E-F865-40DA-9320-16EF77935254"] }, "mockResponse": { "url": "https://ds.uncn.jp", diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json b/adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json index 795afe20084..568369b72d4 100644 --- a/adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json @@ -155,7 +155,8 @@ "user": { "gender": "O" } - } + }, + "impIDs":["29D2F33E-F865-40DA-9320-16EF77935254"] }, "mockResponse": { "url": "https://ds.uncn.jp", diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app.json b/adapters/unicorn/unicorntest/exemplary/banner-app.json index ca604895aa3..06d3c54143b 100644 --- a/adapters/unicorn/unicorntest/exemplary/banner-app.json +++ b/adapters/unicorn/unicorntest/exemplary/banner-app.json @@ -156,7 +156,8 @@ "user": { "gender": "O" } - } + }, + "impIDs":["29D2F33E-F865-40DA-9320-16EF77935254"] }, "mockResponse": { "url": "https://ds.uncn.jp", diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app_with_fpd.json b/adapters/unicorn/unicorntest/exemplary/banner-app_with_fpd.json index 668e566490d..7108a1a9894 100644 --- a/adapters/unicorn/unicorntest/exemplary/banner-app_with_fpd.json +++ b/adapters/unicorn/unicorntest/exemplary/banner-app_with_fpd.json @@ -168,7 +168,8 @@ "user": { "gender": "O" } - } + }, + "impIDs":["29D2F33E-F865-40DA-9320-16EF77935254"] }, "mockResponse": { "url": "https://ds.uncn.jp", diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app_with_no_fpd.json b/adapters/unicorn/unicorntest/exemplary/banner-app_with_no_fpd.json index c86c8f4fc98..7df2b8746d2 100644 --- a/adapters/unicorn/unicorntest/exemplary/banner-app_with_no_fpd.json +++ b/adapters/unicorn/unicorntest/exemplary/banner-app_with_no_fpd.json @@ -162,7 +162,8 @@ "user": { "gender": "O" } - } + }, + "impIDs":["29D2F33E-F865-40DA-9320-16EF77935254"] }, "mockResponse": { "url": "https://ds.uncn.jp", diff --git a/adapters/unicorn/unicorntest/supplemental/204.json b/adapters/unicorn/unicorntest/supplemental/204.json index 1c168a9defc..ece41ee7af0 100644 --- a/adapters/unicorn/unicorntest/supplemental/204.json +++ b/adapters/unicorn/unicorntest/supplemental/204.json @@ -156,7 +156,8 @@ "user": { "gender": "O" } - } + }, + "impIDs":["29D2F33E-F865-40DA-9320-16EF77935254"] }, "mockResponse": { "status": 204, diff --git a/adapters/unicorn/unicorntest/supplemental/400.json b/adapters/unicorn/unicorntest/supplemental/400.json index f41a117f15c..47d716a6e02 100644 --- a/adapters/unicorn/unicorntest/supplemental/400.json +++ b/adapters/unicorn/unicorntest/supplemental/400.json @@ -156,7 +156,8 @@ "user": { "gender": "O" } - } + }, + "impIDs":["29D2F33E-F865-40DA-9320-16EF77935254"] }, "mockResponse": { "status": 400, diff --git a/adapters/unicorn/unicorntest/supplemental/500.json b/adapters/unicorn/unicorntest/supplemental/500.json index 31b977e4256..1deb7c73091 100644 --- a/adapters/unicorn/unicorntest/supplemental/500.json +++ b/adapters/unicorn/unicorntest/supplemental/500.json @@ -156,7 +156,8 @@ "user": { "gender": "O" } - } + }, + "impIDs":["29D2F33E-F865-40DA-9320-16EF77935254"] }, "mockResponse": { "status": 500, diff --git a/adapters/unicorn/unicorntest/supplemental/cannot-parse-accountid.json b/adapters/unicorn/unicorntest/supplemental/cannot-parse-accountid.json index 9ece314b485..93f038e2a0e 100644 --- a/adapters/unicorn/unicorntest/supplemental/cannot-parse-accountid.json +++ b/adapters/unicorn/unicorntest/supplemental/cannot-parse-accountid.json @@ -68,8 +68,8 @@ }, "expectedMakeRequestsErrors": [ { - "value": "Error while decoding imp[0].ext: json: cannot unmarshal string into Go struct field ExtImpUnicorn.bidder.accountId of type int", - "comparison": "literal" + "value": "Error while decoding imp[0].ext: cannot unmarshal openrtb_ext.ExtImpUnicorn.AccountID: unexpected character", + "comparison": "startswith" } ] } diff --git a/adapters/unicorn/unicorntest/supplemental/no-imp-ext.json b/adapters/unicorn/unicorntest/supplemental/no-imp-ext.json index bab6e8d9603..4acfc734f97 100644 --- a/adapters/unicorn/unicorntest/supplemental/no-imp-ext.json +++ b/adapters/unicorn/unicorntest/supplemental/no-imp-ext.json @@ -55,8 +55,8 @@ }, "expectedMakeRequestsErrors": [ { - "value": "Error while decoding imp[0].ext: unexpected end of JSON input", - "comparison": "literal" + "value": "Error while decoding imp[0].ext: expect { or n, but found", + "comparison": "startswith" } ] } diff --git a/adapters/unruly/params_test.go b/adapters/unruly/params_test.go index e9607358a59..132afee8e1f 100644 --- a/adapters/unruly/params_test.go +++ b/adapters/unruly/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/unruly/unruly.go b/adapters/unruly/unruly.go index 1f4bf6b0203..c8e73de99bf 100644 --- a/adapters/unruly/unruly.go +++ b/adapters/unruly/unruly.go @@ -5,11 +5,12 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { @@ -43,6 +44,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E Uri: a.endPoint, Body: reqJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }}, errs } } @@ -54,7 +56,7 @@ func (a *adapter) preProcess(req *openrtb2.BidRequest, errors []error) (*openrtb for i := 0; i < numRequests; i++ { imp := req.Imp[i] var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { err = &errortypes.BadInput{ Message: fmt.Sprintf("ext data not provided in imp id=%s. Abort all Request", imp.ID), } @@ -62,7 +64,7 @@ func (a *adapter) preProcess(req *openrtb2.BidRequest, errors []error) (*openrtb return nil, errors } var unrulyExt openrtb_ext.ExtImpUnruly - if err := json.Unmarshal(bidderExt.Bidder, &unrulyExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &unrulyExt); err != nil { err = &errortypes.BadInput{ Message: fmt.Sprintf("siteid not provided in imp id=%s. Abort all Request", imp.ID), } @@ -106,13 +108,13 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest }} } var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("bad server response: %d. ", err), }} } - bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(internalRequest.Imp)) var errs []error for _, sb := range bidResp.SeatBid { @@ -121,10 +123,16 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest if err != nil { errs = append(errs, err...) } else { - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + bid := adapters.TypedBid{ Bid: &sb.Bid[i], BidType: bidType, - }) + } + if bidType == openrtb_ext.BidTypeVideo && sb.Bid[i].Dur > 0 { + bid.BidVideo = &openrtb_ext.ExtBidPrebidVideo{ + Duration: int(sb.Bid[i].Dur), + } + } + bidResponse.Bids = append(bidResponse.Bids, &bid) } } } diff --git a/adapters/unruly/unruly_test.go b/adapters/unruly/unruly_test.go index b5d837abea5..e3df3d88818 100644 --- a/adapters/unruly/unruly_test.go +++ b/adapters/unruly/unruly_test.go @@ -3,9 +3,9 @@ package unruly import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/unruly/unrulytest/exemplary/banner-and-video-app.json b/adapters/unruly/unrulytest/exemplary/banner-and-video-app.json index 65fc74a7142..4267e08bb7c 100644 --- a/adapters/unruly/unrulytest/exemplary/banner-and-video-app.json +++ b/adapters/unruly/unrulytest/exemplary/banner-and-video-app.json @@ -150,7 +150,8 @@ "connectiontype": 3, "ifa": "AA000DFE74168477C70D291f574D344790E0BB11" } - } + }, + "impIDs":["test-imp-id","test-imp-video-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/unruly/unrulytest/exemplary/banner-and-video-gdpr.json b/adapters/unruly/unrulytest/exemplary/banner-and-video-gdpr.json index 8870fc30e5c..6b8b305e155 100644 --- a/adapters/unruly/unrulytest/exemplary/banner-and-video-gdpr.json +++ b/adapters/unruly/unrulytest/exemplary/banner-and-video-gdpr.json @@ -120,7 +120,8 @@ "gdpr": 1 } } - } + }, + "impIDs":["test-imp-id","test-imp-video-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/unruly/unrulytest/exemplary/banner-and-video-site.json b/adapters/unruly/unrulytest/exemplary/banner-and-video-site.json index bb697ff2294..f786fc6a502 100644 --- a/adapters/unruly/unrulytest/exemplary/banner-and-video-site.json +++ b/adapters/unruly/unrulytest/exemplary/banner-and-video-site.json @@ -130,7 +130,8 @@ "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version / 5.1 .7 Safari / 534.57 .2", "ip": "123.145.167.10" } - } + }, + "impIDs":["test-imp-id","test-imp-video-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/unruly/unrulytest/exemplary/banner-and-video.json b/adapters/unruly/unrulytest/exemplary/banner-and-video.json index 6fa7df52398..72108a16b91 100644 --- a/adapters/unruly/unrulytest/exemplary/banner-and-video.json +++ b/adapters/unruly/unrulytest/exemplary/banner-and-video.json @@ -128,7 +128,8 @@ } } ] - } + }, + "impIDs":["test-imp-id","test-imp-video-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/unruly/unrulytest/exemplary/simple-banner.json b/adapters/unruly/unrulytest/exemplary/simple-banner.json index 49d970f6bae..e483fe4a158 100644 --- a/adapters/unruly/unrulytest/exemplary/simple-banner.json +++ b/adapters/unruly/unrulytest/exemplary/simple-banner.json @@ -53,7 +53,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/unruly/unrulytest/exemplary/simple-video.json b/adapters/unruly/unrulytest/exemplary/simple-video.json index 1f536fe25bb..857a1c5cb3c 100644 --- a/adapters/unruly/unrulytest/exemplary/simple-video.json +++ b/adapters/unruly/unrulytest/exemplary/simple-video.json @@ -9,7 +9,7 @@ "video/mp4" ], "minduration": 1, - "maxduration": 2, + "maxduration": 60, "protocols": [1,3,5], "w": 1020, "h": 780, @@ -41,7 +41,7 @@ "video/mp4" ], "minduration": 1, - "maxduration": 2, + "maxduration": 60, "protocols": [1,3,5], "w": 1020, "h": 780, @@ -59,7 +59,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -77,7 +78,8 @@ "adm": "some-test-ad", "crid": "crid_10", "w": 1024, - "h": 576 + "h": 576, + "dur": 30 } ] } @@ -96,7 +98,8 @@ "adm": "some-test-ad", "crid": "crid_10", "w": 1024, - "h": 576 + "h": 576, + "dur": 30 }, "type": "video" }] diff --git a/adapters/unruly/unrulytest/supplemental/no-matching-impid.json b/adapters/unruly/unrulytest/supplemental/no-matching-impid.json index efabbe64cfe..6be41b833c2 100644 --- a/adapters/unruly/unrulytest/supplemental/no-matching-impid.json +++ b/adapters/unruly/unrulytest/supplemental/no-matching-impid.json @@ -60,7 +60,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/unruly/unrulytest/supplemental/status-code-204.json b/adapters/unruly/unrulytest/supplemental/status-code-204.json index 70dc8646adf..ba48fc955a6 100644 --- a/adapters/unruly/unrulytest/supplemental/status-code-204.json +++ b/adapters/unruly/unrulytest/supplemental/status-code-204.json @@ -60,7 +60,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/unruly/unrulytest/supplemental/status-code-400.json b/adapters/unruly/unrulytest/supplemental/status-code-400.json index a811d242eb9..9a0fd693c7d 100644 --- a/adapters/unruly/unrulytest/supplemental/status-code-400.json +++ b/adapters/unruly/unrulytest/supplemental/status-code-400.json @@ -73,7 +73,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400 diff --git a/adapters/unruly/unrulytest/supplemental/status-code-401.json b/adapters/unruly/unrulytest/supplemental/status-code-401.json index 759f2b5baf8..52fe38d2155 100644 --- a/adapters/unruly/unrulytest/supplemental/status-code-401.json +++ b/adapters/unruly/unrulytest/supplemental/status-code-401.json @@ -60,7 +60,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 401 diff --git a/adapters/vidazoo/params_test.go b/adapters/vidazoo/params_test.go new file mode 100644 index 00000000000..cfd3e9b5d40 --- /dev/null +++ b/adapters/vidazoo/params_test.go @@ -0,0 +1,42 @@ +package vidazoo + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderVidazoo, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected valid params: %s", validParam) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderVidazoo, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"cId": "provided_cid_123"}`, +} + +var invalidParams = []string{ + `{"cId": 3898334}`, +} diff --git a/adapters/vidazoo/vidazoo.go b/adapters/vidazoo/vidazoo.go new file mode 100644 index 00000000000..be0cf66d9d4 --- /dev/null +++ b/adapters/vidazoo/vidazoo.go @@ -0,0 +1,134 @@ +package vidazoo + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +type adapter struct { + endpoint string +} + +// Builder builds a new instance of the Vidazoo for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var requests []*adapters.RequestData + var errors []error + + requestCopy := *request + + for _, imp := range request.Imp { + requestCopy.Imp = []openrtb2.Imp{imp} + + requestJSON, err := json.Marshal(&requestCopy) + if err != nil { + errors = append(errors, fmt.Errorf("marshal bidRequest: %w", err)) + continue + } + + cId, err := extractCid(&imp) + if err != nil { + errors = append(errors, fmt.Errorf("extract cId: %w", err)) + continue + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: fmt.Sprintf("%s%s", a.endpoint, url.QueryEscape(cId)), + Body: requestJSON, + Headers: headers, + ImpIDs: []string{imp.ID}, + } + + requests = append(requests, requestData) + } + + return requests, errors +} + +func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + } + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Could not define bid type for imp: %s", bid.ImpID), + } +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var errs []error + + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", responseData.StatusCode), + }} + } + + var response openrtb2.BidResponse + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("bad server response: %d. ", err), + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(response.SeatBid)) + + if response.Cur != "" { + bidResponse.Currency = response.Cur + } + + for _, seatBid := range response.SeatBid { + for i, bid := range seatBid.Bid { + bidType, err := getMediaTypeForBid(bid) + if err != nil { + errs = append(errs, err) + continue + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + }) + } + } + + return bidResponse, errs +} + +func extractCid(imp *openrtb2.Imp) (string, error) { + var bidderExt adapters.ExtImpBidder + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { + return "", fmt.Errorf("unmarshal bidderExt: %w", err) + } + + var impExt openrtb_ext.ImpExtVidazoo + if err := jsonutil.Unmarshal(bidderExt.Bidder, &impExt); err != nil { + return "", fmt.Errorf("unmarshal ImpExtVidazoo: %w", err) + } + return strings.TrimSpace(impExt.ConnectionId), nil +} diff --git a/adapters/vidazoo/vidazoo_test.go b/adapters/vidazoo/vidazoo_test.go new file mode 100644 index 00000000000..5d6090de95c --- /dev/null +++ b/adapters/vidazoo/vidazoo_test.go @@ -0,0 +1,24 @@ +package vidazoo + +import ( + "testing" + + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderVidazoo, config.Adapter{ + Endpoint: "http://prebid-server.cootlogix.com/openrtb/", + }, + config.Server{ + ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2", + }) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "vidazootest", bidder) +} diff --git a/adapters/vidazoo/vidazootest/exemplary/banner.json b/adapters/vidazoo/vidazootest/exemplary/banner.json new file mode 100644 index 00000000000..98b145181f4 --- /dev/null +++ b/adapters/vidazoo/vidazootest/exemplary/banner.json @@ -0,0 +1,127 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 240, + "h": 400 + }, + "ext": { + "bidder": { + "cId": "test_cid_123" + } + } + } + ], + "tmax": 5000 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://prebid-server.cootlogix.com/openrtb/test_cid_123", + "body": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 240, + "h": 400 + }, + "ext": { + "bidder": { + "cId": "test_cid_123" + } + } + } + ], + "tmax": 5000 + }, + "impIDs": [ + "some-impression-id" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some-request-id", + "cur": "", + "bidid": "some-bid-id", + "seatbid": [ + { + "bid": [ + { + "exp": 60, + "adm": "
Some creative
", + "burl": "", + "iurl": "http://prebid-server.cootlogix.com/creative.jpg", + "lurl": "", + "nurl": "", + "id": "some-bid-id", + "impid": "some-impression-id", + "h": 400, + "w": 240, + "price": 1, + "dealid": "deal123", + "adomain": [ + "test.com" + ], + "adid": "some-ad-id", + "cid": "test", + "attr": [], + "cat": [], + "crid": "some-creative-id", + "ext": {}, + "hratio": 0, + "language": "", + "protocol": 0, + "qagmediarating": 0, + "tactic": "", + "wratio": 0, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "some-bid-id", + "impid": "some-impression-id", + "price": 1, + "adm": "
Some creative
", + "adid": "some-ad-id", + "cid": "test", + "iurl": "http://prebid-server.cootlogix.com/creative.jpg", + "crid": "some-creative-id", + "adomain": [ + "test.com" + ], + "dealid": "deal123", + "w": 240, + "h": 400, + "exp": 60, + "ext": {}, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/vidazoo/vidazootest/exemplary/multi-imp.json b/adapters/vidazoo/vidazootest/exemplary/multi-imp.json new file mode 100644 index 00000000000..7c0f288f3c8 --- /dev/null +++ b/adapters/vidazoo/vidazootest/exemplary/multi-imp.json @@ -0,0 +1,265 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [ + 1, + 2, + 5 + ], + "w": 300, + "h": 250, + "startdelay": 1, + "placement": 1, + "playbackmethod": [ + 2 + ], + "api": [ + 1, + 2, + 3, + 4 + ] + }, + "ext": { + "bidder": { + "cId": "test_cid_123" + } + } + }, + { + "id": "some-impression-id", + "banner": { + "w": 240, + "h": 400 + }, + "ext": { + "bidder": { + "cId": "test_cid_123" + } + } + } + ], + "tmax": 5000 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://prebid-server.cootlogix.com/openrtb/test_cid_123", + "body": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [ + 1, + 2, + 5 + ], + "w": 300, + "h": 250, + "startdelay": 1, + "placement": 1, + "playbackmethod": [ + 2 + ], + "api": [ + 1, + 2, + 3, + 4 + ] + }, + "ext": { + "bidder": { + "cId": "test_cid_123" + } + } + } + ], + "tmax": 5000 + }, + "impIDs": [ + "some-impression-id" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some-request-id", + "cur": "", + "bidid": "some-bid-id", + "seatbid": [ + { + "bid": [ + { + "exp": 60, + "adm": "Some Ad SystemSome Ad Title00:00:02", + "id": "some-bid-id", + "impid": "some-impression-id", + "h": 250, + "w": 300, + "price": 1, + "dealid": "deal123", + "adomain": [ + "test.com" + ], + "adid": "some-ad-id", + "cid": "test", + "crid": "some-creative-id", + "mtype": 2 + } + ] + } + ] + } + } + }, + { + "expectedRequest": { + "uri": "http://prebid-server.cootlogix.com/openrtb/test_cid_123", + "body": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 240, + "h": 400 + }, + "ext": { + "bidder": { + "cId": "test_cid_123" + } + } + } + ], + "tmax": 5000 + }, + "impIDs": [ + "some-impression-id" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some-request-id", + "cur": "", + "bidid": "some-bid-id", + "seatbid": [ + { + "bid": [ + { + "exp": 60, + "adm": "
Some creative
", + "burl": "", + "iurl": "http://prebid-server.cootlogix.com/creative.jpg", + "lurl": "", + "nurl": "", + "id": "some-bid-id", + "impid": "some-impression-id", + "h": 400, + "w": 240, + "price": 1, + "dealid": "deal123", + "adomain": [ + "test.com" + ], + "adid": "some-ad-id", + "cid": "test", + "attr": [], + "cat": [], + "crid": "some-creative-id", + "ext": {}, + "hratio": 0, + "language": "", + "protocol": 0, + "qagmediarating": 0, + "tactic": "", + "wratio": 0, + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "exp": 60, + "adm": "Some Ad SystemSome Ad Title00:00:02", + "id": "some-bid-id", + "impid": "some-impression-id", + "h": 250, + "w": 300, + "price": 1, + "dealid": "deal123", + "adomain": [ + "test.com" + ], + "adid": "some-ad-id", + "cid": "test", + "crid": "some-creative-id", + "mtype": 2 + }, + "type": "video" + } + ] + }, + { + "bids": [ + { + "bid": { + "id": "some-bid-id", + "impid": "some-impression-id", + "price": 1, + "adm": "
Some creative
", + "adid": "some-ad-id", + "cid": "test", + "iurl": "http://prebid-server.cootlogix.com/creative.jpg", + "crid": "some-creative-id", + "adomain": [ + "test.com" + ], + "dealid": "deal123", + "w": 240, + "h": 400, + "exp": 60, + "ext": {}, + "mtype": 1 + }, + "type": "banner" + } + ] + } + ], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/vidazoo/vidazootest/exemplary/video.json b/adapters/vidazoo/vidazootest/exemplary/video.json new file mode 100644 index 00000000000..2a414476382 --- /dev/null +++ b/adapters/vidazoo/vidazootest/exemplary/video.json @@ -0,0 +1,154 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [ + 1, + 2, + 5 + ], + "w": 300, + "h": 250, + "startdelay": 1, + "placement": 1, + "playbackmethod": [ + 2 + ], + "api": [ + 1, + 2, + 3, + 4 + ] + }, + "ext": { + "bidder": { + "cId": "test_cid_123" + } + } + } + ], + "tmax": 5000 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://prebid-server.cootlogix.com/openrtb/test_cid_123", + "body": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [ + 1, + 2, + 5 + ], + "w": 300, + "h": 250, + "startdelay": 1, + "placement": 1, + "playbackmethod": [ + 2 + ], + "api": [ + 1, + 2, + 3, + 4 + ] + }, + "ext": { + "bidder": { + "cId": "test_cid_123" + } + } + } + ], + "tmax": 5000 + }, + "impIDs": [ + "some-impression-id" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some-request-id", + "cur": "", + "bidid": "some-bid-id", + "seatbid": [ + { + "bid": [ + { + "exp": 60, + "adm": "Some Ad SystemSome Ad Title00:00:02", + "id": "some-bid-id", + "impid": "some-impression-id", + "h": 250, + "w": 300, + "price": 1, + "dealid": "deal123", + "adomain": [ + "test.com" + ], + "adid": "some-ad-id", + "cid": "test", + "crid": "some-creative-id", + "mtype": 2 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "exp": 60, + "adm": "Some Ad SystemSome Ad Title00:00:02", + "id": "some-bid-id", + "impid": "some-impression-id", + "h": 250, + "w": 300, + "price": 1, + "dealid": "deal123", + "adomain": [ + "test.com" + ], + "adid": "some-ad-id", + "cid": "test", + "crid": "some-creative-id", + "mtype": 2 + }, + "type": "video" + } + ] + } + ], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/vidazoo/vidazootest/supplemental/bad-request.json b/adapters/vidazoo/vidazootest/supplemental/bad-request.json new file mode 100644 index 00000000000..864fe64c619 --- /dev/null +++ b/adapters/vidazoo/vidazootest/supplemental/bad-request.json @@ -0,0 +1,65 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 240, + "h": 400 + }, + "ext": { + "bidder": { + "cid": "test_cid_123" + } + } + } + ], + "tmax": 5000 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://prebid-server.cootlogix.com/openrtb/test_cid_123", + "body": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 240, + "h": 400 + }, + "ext": { + "bidder": { + "cid": "test_cid_123" + } + } + } + ], + "tmax": 5000 + }, + "impIDs": [ + "some-impression-id" + ] + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/vidazoo/vidazootest/supplemental/internal-error.json b/adapters/vidazoo/vidazootest/supplemental/internal-error.json new file mode 100644 index 00000000000..92dc89f08c0 --- /dev/null +++ b/adapters/vidazoo/vidazootest/supplemental/internal-error.json @@ -0,0 +1,65 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 240, + "h": 400 + }, + "ext": { + "bidder": { + "cid": "test_cid_123" + } + } + } + ], + "tmax": 5000 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://prebid-server.cootlogix.com/openrtb/test_cid_123", + "body": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 240, + "h": 400 + }, + "ext": { + "bidder": { + "cid": "test_cid_123" + } + } + } + ], + "tmax": 5000 + }, + "impIDs": [ + "some-impression-id" + ] + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/vidazoo/vidazootest/supplemental/no-content.json b/adapters/vidazoo/vidazootest/supplemental/no-content.json new file mode 100644 index 00000000000..6d8e7e9bec1 --- /dev/null +++ b/adapters/vidazoo/vidazootest/supplemental/no-content.json @@ -0,0 +1,60 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 240, + "h": 400 + }, + "ext": { + "bidder": { + "cid": "test_cid_123" + } + } + } + ], + "tmax": 5000 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://prebid-server.cootlogix.com/openrtb/test_cid_123", + "body": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 240, + "h": 400 + }, + "ext": { + "bidder": { + "cid": "test_cid_123" + } + } + } + ], + "tmax": 5000 + }, + "impIDs": [ + "some-impression-id" + ] + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/vidazoo/vidazootest/supplemental/unknown-bid-type.json b/adapters/vidazoo/vidazootest/supplemental/unknown-bid-type.json new file mode 100644 index 00000000000..5f3ec80799e --- /dev/null +++ b/adapters/vidazoo/vidazootest/supplemental/unknown-bid-type.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 240, + "h": 400 + }, + "ext": { + "bidder": { + "cId": "test_cid_123" + } + } + } + ], + "tmax": 5000 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://prebid-server.cootlogix.com/openrtb/test_cid_123", + "body": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 240, + "h": 400 + }, + "ext": { + "bidder": { + "cId": "test_cid_123" + } + } + } + ], + "tmax": 5000 + }, + "impIDs": [ + "some-impression-id" + ] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some-request-id", + "cur": "", + "bidid": "some-bid-id", + "seatbid": [ + { + "bid": [ + { + "exp": 60, + "adm": "
Some creative
", + "burl": "", + "iurl": "http://prebid-server.cootlogix.com/creative.jpg", + "lurl": "", + "nurl": "", + "id": "some-bid-id", + "impid": "some-impression-id", + "h": 400, + "w": 240, + "price": 1, + "dealid": "deal123", + "adomain": [ + "test.com" + ], + "adid": "some-ad-id", + "cid": "test", + "attr": [], + "cat": [], + "crid": "some-creative-id", + "ext": {}, + "hratio": 0, + "language": "", + "protocol": 0, + "qagmediarating": 0, + "tactic": "", + "wratio": 0, + "mtype": 8 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Could not define bid type for imp: some-impression-id", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/videobyte/params_test.go b/adapters/videobyte/params_test.go index b638d4585c6..ffb37f88d97 100644 --- a/adapters/videobyte/params_test.go +++ b/adapters/videobyte/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // This file actually intends to test static/bidder-params/videobyte.json diff --git a/adapters/videobyte/videobyte.go b/adapters/videobyte/videobyte.go index 2dc6df84895..ed79c6525bd 100644 --- a/adapters/videobyte/videobyte.go +++ b/adapters/videobyte/videobyte.go @@ -6,12 +6,13 @@ import ( "net/http" "net/url" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" ) type adapter struct { @@ -49,6 +50,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E Uri: a.endpoint + "?" + getParams(impExt).Encode(), Body: body, Headers: getHeaders(request), + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }) } @@ -74,7 +76,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest } var ortbResponse openrtb2.BidResponse - err := json.Unmarshal(response.Body, &ortbResponse) + err := jsonutil.Unmarshal(response.Body, &ortbResponse) if err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: "Bad Server Response", @@ -140,14 +142,14 @@ func getHeaders(request *openrtb2.BidRequest) http.Header { func parseExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpVideoByte, error) { var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ Message: fmt.Sprintf("Ignoring imp id=%s, error while decoding extImpBidder, err: %s", imp.ID, err), } } impExt := openrtb_ext.ExtImpVideoByte{} - err := json.Unmarshal(bidderExt.Bidder, &impExt) + err := jsonutil.Unmarshal(bidderExt.Bidder, &impExt) if err != nil { return nil, &errortypes.BadInput{ Message: fmt.Sprintf("Ignoring imp id=%s, error while decoding impExt, err: %s", imp.ID, err), diff --git a/adapters/videobyte/videobyte_test.go b/adapters/videobyte/videobyte_test.go index d4dda0606f8..2d1d2a07fda 100644 --- a/adapters/videobyte/videobyte_test.go +++ b/adapters/videobyte/videobyte_test.go @@ -3,9 +3,9 @@ package videobyte import ( "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v3/config" - "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/videobyte/videobytetest/exemplary/banner.json b/adapters/videobyte/videobytetest/exemplary/banner.json index ecf733cca56..b0ea5923f4b 100644 --- a/adapters/videobyte/videobytetest/exemplary/banner.json +++ b/adapters/videobyte/videobytetest/exemplary/banner.json @@ -91,7 +91,8 @@ "page": "http://example.com/page-1", "ref": "http://referer.com/page-2" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/videobyte/videobytetest/exemplary/empty-placement-network.json b/adapters/videobyte/videobytetest/exemplary/empty-placement-network.json index ddda5aa2a4c..3b67784ee08 100644 --- a/adapters/videobyte/videobytetest/exemplary/empty-placement-network.json +++ b/adapters/videobyte/videobytetest/exemplary/empty-placement-network.json @@ -95,7 +95,8 @@ "page": "http://example.com/page-1", "ref": "http://referer.com/page-2" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/videobyte/videobytetest/exemplary/empty-site-domain-ref.json b/adapters/videobyte/videobytetest/exemplary/empty-site-domain-ref.json index e10a4aa52f4..18c6f14f425 100644 --- a/adapters/videobyte/videobytetest/exemplary/empty-site-domain-ref.json +++ b/adapters/videobyte/videobytetest/exemplary/empty-site-domain-ref.json @@ -89,7 +89,8 @@ "site": { "page": "http://example.com/page-1" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/videobyte/videobytetest/exemplary/multi-format.json b/adapters/videobyte/videobytetest/exemplary/multi-format.json index cf62ad79dff..c4cebda6b88 100644 --- a/adapters/videobyte/videobytetest/exemplary/multi-format.json +++ b/adapters/videobyte/videobytetest/exemplary/multi-format.json @@ -107,7 +107,8 @@ "page": "http://example.com/page-1", "ref": "http://referer.com/page-2" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/videobyte/videobytetest/exemplary/multi-imp.json b/adapters/videobyte/videobytetest/exemplary/multi-imp.json index 547a86da6a2..dd525c4a4c6 100644 --- a/adapters/videobyte/videobytetest/exemplary/multi-imp.json +++ b/adapters/videobyte/videobytetest/exemplary/multi-imp.json @@ -117,7 +117,8 @@ "page": "http://example.com/page-1", "ref": "http://referer.com/page-2" } - } + }, + "impIDs":["test-imp-id-1"] }, "mockResponse": { "status": 200, @@ -204,7 +205,8 @@ "page": "http://example.com/page-1", "ref": "http://referer.com/page-2" } - } + }, + "impIDs":["test-imp-id-2"] }, "mockResponse": { "status": 200, diff --git a/adapters/videobyte/videobytetest/exemplary/video.json b/adapters/videobyte/videobytetest/exemplary/video.json index 7cb07808020..41dd0486afc 100644 --- a/adapters/videobyte/videobytetest/exemplary/video.json +++ b/adapters/videobyte/videobytetest/exemplary/video.json @@ -99,7 +99,8 @@ "page": "http://example.com/page-1", "ref": "http://referer.com/page-2" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext-bidder.json b/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext-bidder.json index 1b806ed57ab..d7d8460bb1b 100644 --- a/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext-bidder.json +++ b/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext-bidder.json @@ -38,7 +38,7 @@ }, "expectedMakeRequestsErrors": [ { - "value": "Ignoring imp id=test-imp-id, error while decoding impExt, err: json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpVideoByte", + "value": "Ignoring imp id=test-imp-id, error while decoding impExt, err: expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext.json b/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext.json index 199f4c5570c..a24ea9b924f 100644 --- a/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext.json +++ b/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext.json @@ -36,7 +36,7 @@ }, "expectedMakeRequestsErrors": [ { - "value": "Ignoring imp id=test-imp-id, error while decoding extImpBidder, err: json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "value": "Ignoring imp id=test-imp-id, error while decoding extImpBidder, err: expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/videobyte/videobytetest/supplemental/invalid-response.json b/adapters/videobyte/videobytetest/supplemental/invalid-response.json index 35bce9a732e..43a4c9f7cbf 100644 --- a/adapters/videobyte/videobytetest/supplemental/invalid-response.json +++ b/adapters/videobyte/videobytetest/supplemental/invalid-response.json @@ -99,7 +99,8 @@ "page": "http://example.com/page-1", "ref": "http://referer.com/page-2" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/videobyte/videobytetest/supplemental/status-code-bad-request.json b/adapters/videobyte/videobytetest/supplemental/status-code-bad-request.json index 09ad1bbb2fc..48eeac4f3ea 100644 --- a/adapters/videobyte/videobytetest/supplemental/status-code-bad-request.json +++ b/adapters/videobyte/videobytetest/supplemental/status-code-bad-request.json @@ -99,7 +99,8 @@ "page": "http://example.com/page-1", "ref": "http://referer.com/page-2" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400 diff --git a/adapters/videobyte/videobytetest/supplemental/status-code-no-content.json b/adapters/videobyte/videobytetest/supplemental/status-code-no-content.json index eaa61c74877..f199fce87b9 100644 --- a/adapters/videobyte/videobytetest/supplemental/status-code-no-content.json +++ b/adapters/videobyte/videobytetest/supplemental/status-code-no-content.json @@ -99,7 +99,8 @@ "page": "http://example.com/page-1", "ref": "http://referer.com/page-2" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/videobyte/videobytetest/supplemental/status-code-other-error.json b/adapters/videobyte/videobytetest/supplemental/status-code-other-error.json index d8a1c1f4034..60218b1af8c 100644 --- a/adapters/videobyte/videobytetest/supplemental/status-code-other-error.json +++ b/adapters/videobyte/videobytetest/supplemental/status-code-other-error.json @@ -99,7 +99,8 @@ "page": "http://example.com/page-1", "ref": "http://referer.com/page-2" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 505 diff --git a/adapters/videoheroes/params_test.go b/adapters/videoheroes/params_test.go index d79f83245a4..e8af1f1fcf5 100644 --- a/adapters/videoheroes/params_test.go +++ b/adapters/videoheroes/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/videoheroes/videoheroes.go b/adapters/videoheroes/videoheroes.go index d4efcab2c90..8d1d7450cb3 100755 --- a/adapters/videoheroes/videoheroes.go +++ b/adapters/videoheroes/videoheroes.go @@ -6,12 +6,13 @@ import ( "net/http" "text/template" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { @@ -61,18 +62,19 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte Body: reqJSON, Uri: url, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }}, nil } func (a *adapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpVideoHeroes, error) { var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ Message: "ext.bidder not provided", } } var videoHeroesExt openrtb_ext.ExtImpVideoHeroes - if err := json.Unmarshal(bidderExt.Bidder, &videoHeroesExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &videoHeroesExt); err != nil { return nil, &errortypes.BadInput{ Message: "ext.bidder not provided", } @@ -117,7 +119,7 @@ func (a *adapter) MakeBids( } var bidResponse openrtb2.BidResponse - if err := json.Unmarshal(bidderResponse.Body, &bidResponse); err != nil { + if err := jsonutil.Unmarshal(bidderResponse.Body, &bidResponse); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: "Bad Server Response", }} diff --git a/adapters/videoheroes/videoheroes_test.go b/adapters/videoheroes/videoheroes_test.go index ac60d56e175..3545b601dc6 100644 --- a/adapters/videoheroes/videoheroes_test.go +++ b/adapters/videoheroes/videoheroes_test.go @@ -3,9 +3,9 @@ package videoheroes import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/videoheroes/videoheroestest/exemplary/banner-app.json b/adapters/videoheroes/videoheroestest/exemplary/banner-app.json index 17d3add92f4..f31b620b8a0 100644 --- a/adapters/videoheroes/videoheroestest/exemplary/banner-app.json +++ b/adapters/videoheroes/videoheroestest/exemplary/banner-app.json @@ -81,7 +81,8 @@ "id": "some-user" }, "tmax": 1000 - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/videoheroes/videoheroestest/exemplary/banner-web.json b/adapters/videoheroes/videoheroestest/exemplary/banner-web.json index f838716340c..04f569ec267 100644 --- a/adapters/videoheroes/videoheroestest/exemplary/banner-web.json +++ b/adapters/videoheroes/videoheroestest/exemplary/banner-web.json @@ -79,7 +79,8 @@ "id": "some-user" }, "tmax": 1000 - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/videoheroes/videoheroestest/exemplary/native-app.json b/adapters/videoheroes/videoheroestest/exemplary/native-app.json index cce8010940f..4eaf3d30695 100644 --- a/adapters/videoheroes/videoheroestest/exemplary/native-app.json +++ b/adapters/videoheroes/videoheroestest/exemplary/native-app.json @@ -81,7 +81,8 @@ "id": "some-user" }, "tmax": 1000 - } + }, + "impIDs":["impression-id"] }, "mockResponse": { diff --git a/adapters/videoheroes/videoheroestest/exemplary/native-web.json b/adapters/videoheroes/videoheroestest/exemplary/native-web.json index 381bac5ce82..186056527af 100644 --- a/adapters/videoheroes/videoheroestest/exemplary/native-web.json +++ b/adapters/videoheroes/videoheroestest/exemplary/native-web.json @@ -79,7 +79,8 @@ "id": "some-user" }, "tmax": 1000 - } + }, + "impIDs":["impression-id"] }, "mockResponse": { diff --git a/adapters/videoheroes/videoheroestest/exemplary/video-app.json b/adapters/videoheroes/videoheroestest/exemplary/video-app.json index 96dd555d585..8d8c9d61d7f 100644 --- a/adapters/videoheroes/videoheroestest/exemplary/video-app.json +++ b/adapters/videoheroes/videoheroestest/exemplary/video-app.json @@ -91,7 +91,8 @@ "id": "some-user" }, "tmax": 1000 - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/videoheroes/videoheroestest/exemplary/video-web.json b/adapters/videoheroes/videoheroestest/exemplary/video-web.json index 243349a96b5..edd81595c32 100644 --- a/adapters/videoheroes/videoheroestest/exemplary/video-web.json +++ b/adapters/videoheroes/videoheroestest/exemplary/video-web.json @@ -89,7 +89,8 @@ "id": "some-user" }, "tmax": 1000 - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/videoheroes/videoheroestest/supplemental/empty-seatbid-array.json b/adapters/videoheroes/videoheroestest/supplemental/empty-seatbid-array.json index 3743daeaa3f..87bca881ba5 100644 --- a/adapters/videoheroes/videoheroestest/supplemental/empty-seatbid-array.json +++ b/adapters/videoheroes/videoheroestest/supplemental/empty-seatbid-array.json @@ -89,7 +89,8 @@ "id": "some-user" }, "tmax": 1000 - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/videoheroes/videoheroestest/supplemental/invalid-response.json b/adapters/videoheroes/videoheroestest/supplemental/invalid-response.json index ad9a32c0bbe..0aa7c11ead3 100644 --- a/adapters/videoheroes/videoheroestest/supplemental/invalid-response.json +++ b/adapters/videoheroes/videoheroestest/supplemental/invalid-response.json @@ -82,7 +82,8 @@ "id": "some-user" }, "tmax": 1000 - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/videoheroes/videoheroestest/supplemental/status-code-bad-request.json b/adapters/videoheroes/videoheroestest/supplemental/status-code-bad-request.json index 432540e399c..0168e7f6724 100644 --- a/adapters/videoheroes/videoheroestest/supplemental/status-code-bad-request.json +++ b/adapters/videoheroes/videoheroestest/supplemental/status-code-bad-request.json @@ -92,7 +92,8 @@ "id": "some-user" }, "tmax": 1000 - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 400 diff --git a/adapters/videoheroes/videoheroestest/supplemental/status-code-no-content.json b/adapters/videoheroes/videoheroestest/supplemental/status-code-no-content.json index bec17d52e8c..ec0016c2ed4 100644 --- a/adapters/videoheroes/videoheroestest/supplemental/status-code-no-content.json +++ b/adapters/videoheroes/videoheroestest/supplemental/status-code-no-content.json @@ -92,7 +92,8 @@ "id": "some-user" }, "tmax": 1000 - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/videoheroes/videoheroestest/supplemental/status-code-other-error.json b/adapters/videoheroes/videoheroestest/supplemental/status-code-other-error.json index cb37a4d93f8..66eaadf9873 100644 --- a/adapters/videoheroes/videoheroestest/supplemental/status-code-other-error.json +++ b/adapters/videoheroes/videoheroestest/supplemental/status-code-other-error.json @@ -92,7 +92,8 @@ "id": "some-user" }, "tmax": 1000 - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 306 diff --git a/adapters/videoheroes/videoheroestest/supplemental/status-code-service-unavailable.json b/adapters/videoheroes/videoheroestest/supplemental/status-code-service-unavailable.json index 38d14f4faaa..380809a25b1 100644 --- a/adapters/videoheroes/videoheroestest/supplemental/status-code-service-unavailable.json +++ b/adapters/videoheroes/videoheroestest/supplemental/status-code-service-unavailable.json @@ -91,7 +91,8 @@ "id": "some-user" }, "tmax": 1000 - } + }, + "impIDs":["impression-id"] }, "mockResponse": { "status": 503 diff --git a/adapters/vidoomy/params_test.go b/adapters/vidoomy/params_test.go index 63ffb462c19..b3801c637c8 100644 --- a/adapters/vidoomy/params_test.go +++ b/adapters/vidoomy/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // This file actually intends to test static/bidder-params/vidoomy.json diff --git a/adapters/vidoomy/vidoomy.go b/adapters/vidoomy/vidoomy.go index 7e7e9d64eb3..b39448a4143 100644 --- a/adapters/vidoomy/vidoomy.go +++ b/adapters/vidoomy/vidoomy.go @@ -5,12 +5,14 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" ) type adapter struct { @@ -46,6 +48,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E Uri: a.endpoint, Body: reqJSON, Headers: header, + ImpIDs: openrtb_ext.GetImpIDs(reqCopy.Imp), }) } @@ -97,8 +100,8 @@ func changeRequestForBidService(request *openrtb2.BidRequest) error { return fmt.Errorf("no sizes provided for Banner %v", banner.Format) } - banner.W = openrtb2.Int64Ptr(banner.Format[0].W) - banner.H = openrtb2.Int64Ptr(banner.Format[0].H) + banner.W = ptrutil.ToPtr(banner.Format[0].W) + banner.H = ptrutil.ToPtr(banner.Format[0].H) return nil } @@ -115,7 +118,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest } var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("Bad server response: %d.", err), }} diff --git a/adapters/vidoomy/vidoomy_test.go b/adapters/vidoomy/vidoomy_test.go index 60cd2c9d967..ef561b596b6 100644 --- a/adapters/vidoomy/vidoomy_test.go +++ b/adapters/vidoomy/vidoomy_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestVidoomyBidderEndpointConfig(t *testing.T) { diff --git a/adapters/vidoomy/vidoomytest/exemplary/multi-impression-video-banner.json b/adapters/vidoomy/vidoomytest/exemplary/multi-impression-video-banner.json index 723248ddebb..a84e7553300 100644 --- a/adapters/vidoomy/vidoomytest/exemplary/multi-impression-video-banner.json +++ b/adapters/vidoomy/vidoomytest/exemplary/multi-impression-video-banner.json @@ -84,7 +84,8 @@ "site": { "id": "test-site-id" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -155,7 +156,8 @@ "site": { "id": "test-site-id" } - } + }, + "impIDs":["test-imp-video-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/vidoomy/vidoomytest/exemplary/simple-app-banner.json b/adapters/vidoomy/vidoomytest/exemplary/simple-app-banner.json index 145524ea4d7..162a0337c78 100644 --- a/adapters/vidoomy/vidoomytest/exemplary/simple-app-banner.json +++ b/adapters/vidoomy/vidoomytest/exemplary/simple-app-banner.json @@ -68,7 +68,8 @@ "app": { "id": "test-app-id" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/vidoomy/vidoomytest/exemplary/simple-banner.json b/adapters/vidoomy/vidoomytest/exemplary/simple-banner.json index 36d278568dd..dbce8f808a9 100644 --- a/adapters/vidoomy/vidoomytest/exemplary/simple-banner.json +++ b/adapters/vidoomy/vidoomytest/exemplary/simple-banner.json @@ -68,7 +68,8 @@ "site": { "id": "test-site-id" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/vidoomy/vidoomytest/exemplary/simple-site-video.json b/adapters/vidoomy/vidoomytest/exemplary/simple-site-video.json index d2d6e028dc2..f029a2d363e 100644 --- a/adapters/vidoomy/vidoomytest/exemplary/simple-site-video.json +++ b/adapters/vidoomy/vidoomytest/exemplary/simple-site-video.json @@ -68,7 +68,8 @@ "id": "123" } } - } + }, + "impIDs":["test-imp-video-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/vidoomy/vidoomytest/supplemental/server-error.json b/adapters/vidoomy/vidoomytest/supplemental/server-error.json index ebeedb148f2..6db4981617a 100644 --- a/adapters/vidoomy/vidoomytest/supplemental/server-error.json +++ b/adapters/vidoomy/vidoomytest/supplemental/server-error.json @@ -51,7 +51,8 @@ "site": { "id": "site-id" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 500, diff --git a/adapters/vidoomy/vidoomytest/supplemental/server-response-wrong-impid.json b/adapters/vidoomy/vidoomytest/supplemental/server-response-wrong-impid.json index dad8f7cc866..8255a777496 100644 --- a/adapters/vidoomy/vidoomytest/supplemental/server-response-wrong-impid.json +++ b/adapters/vidoomy/vidoomytest/supplemental/server-response-wrong-impid.json @@ -68,7 +68,8 @@ "site": { "id": "site-id" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/vidoomy/vidoomytest/supplemental/simple-banner-no-response.json b/adapters/vidoomy/vidoomytest/supplemental/simple-banner-no-response.json index b765252fb7a..44a2bd103e3 100644 --- a/adapters/vidoomy/vidoomytest/supplemental/simple-banner-no-response.json +++ b/adapters/vidoomy/vidoomytest/supplemental/simple-banner-no-response.json @@ -70,7 +70,8 @@ "site": { "id": "test-site-id" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/visiblemeasures/params_test.go b/adapters/visiblemeasures/params_test.go index 7bcc1cf60cf..b4e5bb66c1d 100644 --- a/adapters/visiblemeasures/params_test.go +++ b/adapters/visiblemeasures/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/visiblemeasures/visiblemeasures.go b/adapters/visiblemeasures/visiblemeasures.go index 3d6a96640e9..100ba03e59b 100644 --- a/adapters/visiblemeasures/visiblemeasures.go +++ b/adapters/visiblemeasures/visiblemeasures.go @@ -5,11 +5,12 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { @@ -44,10 +45,10 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E var bidderExt adapters.ExtImpBidder var visiblemeasuresExt openrtb_ext.ImpExtVisibleMeasures - if err = json.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt); err != nil { + if err = jsonutil.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt); err != nil { return nil, []error{err} } - if err = json.Unmarshal(bidderExt.Bidder, &visiblemeasuresExt); err != nil { + if err = jsonutil.Unmarshal(bidderExt.Bidder, &visiblemeasuresExt); err != nil { return nil, []error{err} } @@ -94,6 +95,7 @@ func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestDa Uri: a.endpoint, Body: reqJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }, nil } @@ -110,7 +112,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R } var response openrtb2.BidResponse - if err := json.Unmarshal(responseData.Body, &response); err != nil { + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { return nil, []error{err} } diff --git a/adapters/visiblemeasures/visiblemeasures_test.go b/adapters/visiblemeasures/visiblemeasures_test.go index 8c1759c010e..82038938358 100644 --- a/adapters/visiblemeasures/visiblemeasures_test.go +++ b/adapters/visiblemeasures/visiblemeasures_test.go @@ -3,9 +3,9 @@ package visiblemeasures import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/visiblemeasures/visiblemeasurestest/exemplary/endpointId.json b/adapters/visiblemeasures/visiblemeasurestest/exemplary/endpointId.json index cb77ecdaf31..c2ff58c26fa 100644 --- a/adapters/visiblemeasures/visiblemeasurestest/exemplary/endpointId.json +++ b/adapters/visiblemeasures/visiblemeasurestest/exemplary/endpointId.json @@ -71,7 +71,8 @@ "ip": "123.123.123.123", "ua": "iPad" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/visiblemeasures/visiblemeasurestest/exemplary/simple-banner.json b/adapters/visiblemeasures/visiblemeasurestest/exemplary/simple-banner.json index 234d0619f6f..4c31d8409c9 100644 --- a/adapters/visiblemeasures/visiblemeasurestest/exemplary/simple-banner.json +++ b/adapters/visiblemeasures/visiblemeasurestest/exemplary/simple-banner.json @@ -71,7 +71,8 @@ "ip": "123.123.123.123", "ua": "iPad" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/visiblemeasures/visiblemeasurestest/exemplary/simple-native.json b/adapters/visiblemeasures/visiblemeasurestest/exemplary/simple-native.json index b54ef078c69..9d951f673a6 100644 --- a/adapters/visiblemeasures/visiblemeasurestest/exemplary/simple-native.json +++ b/adapters/visiblemeasures/visiblemeasurestest/exemplary/simple-native.json @@ -55,7 +55,8 @@ "ip": "123.123.123.123", "ua": "iPad" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/visiblemeasures/visiblemeasurestest/exemplary/simple-video.json b/adapters/visiblemeasures/visiblemeasurestest/exemplary/simple-video.json index 93b592a8049..98f3883174c 100644 --- a/adapters/visiblemeasures/visiblemeasurestest/exemplary/simple-video.json +++ b/adapters/visiblemeasures/visiblemeasurestest/exemplary/simple-video.json @@ -69,7 +69,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/visiblemeasures/visiblemeasurestest/exemplary/simple-web-banner.json b/adapters/visiblemeasures/visiblemeasurestest/exemplary/simple-web-banner.json index e9a30b09c0f..8d86d74e104 100644 --- a/adapters/visiblemeasures/visiblemeasurestest/exemplary/simple-web-banner.json +++ b/adapters/visiblemeasures/visiblemeasurestest/exemplary/simple-web-banner.json @@ -71,7 +71,8 @@ "ip": "123.123.123.123", "ua": "Ubuntu" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/visiblemeasures/visiblemeasurestest/supplemental/bad_media_type.json b/adapters/visiblemeasures/visiblemeasurestest/supplemental/bad_media_type.json index af38ab7c690..367f6d04112 100644 --- a/adapters/visiblemeasures/visiblemeasurestest/supplemental/bad_media_type.json +++ b/adapters/visiblemeasures/visiblemeasurestest/supplemental/bad_media_type.json @@ -44,7 +44,8 @@ "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/visiblemeasures/visiblemeasurestest/supplemental/bad_response.json b/adapters/visiblemeasures/visiblemeasurestest/supplemental/bad_response.json index 8c1cc3750db..5cc4a338ac3 100644 --- a/adapters/visiblemeasures/visiblemeasurestest/supplemental/bad_response.json +++ b/adapters/visiblemeasures/visiblemeasurestest/supplemental/bad_response.json @@ -68,7 +68,8 @@ "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -77,7 +78,7 @@ }], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/visiblemeasures/visiblemeasurestest/supplemental/status-204.json b/adapters/visiblemeasures/visiblemeasurestest/supplemental/status-204.json index 3e536c25a60..b2c822cb446 100644 --- a/adapters/visiblemeasures/visiblemeasurestest/supplemental/status-204.json +++ b/adapters/visiblemeasures/visiblemeasurestest/supplemental/status-204.json @@ -68,7 +68,8 @@ "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/visiblemeasures/visiblemeasurestest/supplemental/status-not-200.json b/adapters/visiblemeasures/visiblemeasurestest/supplemental/status-not-200.json index b60ca2ccb33..30f92ef4180 100644 --- a/adapters/visiblemeasures/visiblemeasurestest/supplemental/status-not-200.json +++ b/adapters/visiblemeasures/visiblemeasurestest/supplemental/status-not-200.json @@ -68,7 +68,8 @@ "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 404, diff --git a/adapters/visx/params_test.go b/adapters/visx/params_test.go index e53d2cda007..1355d6f8d93 100644 --- a/adapters/visx/params_test.go +++ b/adapters/visx/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/visx/visx.go b/adapters/visx/visx.go index 713c2693990..85f3c300a2d 100644 --- a/adapters/visx/visx.go +++ b/adapters/visx/visx.go @@ -5,11 +5,12 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type VisxAdapter struct { @@ -84,6 +85,7 @@ func (a *VisxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapte Uri: a.endpoint, Body: reqJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(requestCopy.Imp), }}, errors } @@ -106,7 +108,7 @@ func (a *VisxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReq } var bidResp visxResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -145,7 +147,7 @@ func getMediaTypeForImp(impID string, imps []openrtb2.Imp, bid visxBid) (openrtb for _, imp := range imps { if imp.ID == impID { var ext visxBidExt - if err := json.Unmarshal(bid.Ext, &ext); err == nil { + if err := jsonutil.Unmarshal(bid.Ext, &ext); err == nil { if ext.Prebid.Meta.MediaType == openrtb_ext.BidTypeBanner { return openrtb_ext.BidTypeBanner, nil } diff --git a/adapters/visx/visx_test.go b/adapters/visx/visx_test.go index 5fc58a1f83d..d2adffddcea 100644 --- a/adapters/visx/visx_test.go +++ b/adapters/visx/visx_test.go @@ -3,9 +3,9 @@ package visx import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/visx/visxtest/exemplary/headers_ipv4.json b/adapters/visx/visxtest/exemplary/headers_ipv4.json index 4a4da7b5673..206ba6bce9e 100644 --- a/adapters/visx/visxtest/exemplary/headers_ipv4.json +++ b/adapters/visx/visxtest/exemplary/headers_ipv4.json @@ -125,7 +125,8 @@ } } } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/visx/visxtest/exemplary/headers_ipv6.json b/adapters/visx/visxtest/exemplary/headers_ipv6.json index a3774c5e771..577d2321711 100644 --- a/adapters/visx/visxtest/exemplary/headers_ipv6.json +++ b/adapters/visx/visxtest/exemplary/headers_ipv6.json @@ -125,7 +125,8 @@ } } } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/visx/visxtest/exemplary/multitype-banner-response.json b/adapters/visx/visxtest/exemplary/multitype-banner-response.json index 0ef214dd372..2ea4030dbcc 100644 --- a/adapters/visx/visxtest/exemplary/multitype-banner-response.json +++ b/adapters/visx/visxtest/exemplary/multitype-banner-response.json @@ -133,7 +133,8 @@ } } } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/visx/visxtest/exemplary/multitype-video-response.json b/adapters/visx/visxtest/exemplary/multitype-video-response.json index 17877315fcf..9beab780bd3 100644 --- a/adapters/visx/visxtest/exemplary/multitype-video-response.json +++ b/adapters/visx/visxtest/exemplary/multitype-video-response.json @@ -133,7 +133,8 @@ } } } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/visx/visxtest/exemplary/simple-banner.json b/adapters/visx/visxtest/exemplary/simple-banner.json index 0f34e6f0325..6971f4e5f47 100644 --- a/adapters/visx/visxtest/exemplary/simple-banner.json +++ b/adapters/visx/visxtest/exemplary/simple-banner.json @@ -121,7 +121,8 @@ } } } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/visx/visxtest/exemplary/simple-video.json b/adapters/visx/visxtest/exemplary/simple-video.json index b39dad74a08..21a66f464a7 100644 --- a/adapters/visx/visxtest/exemplary/simple-video.json +++ b/adapters/visx/visxtest/exemplary/simple-video.json @@ -121,7 +121,8 @@ } } } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/visx/visxtest/exemplary/with_currency.json b/adapters/visx/visxtest/exemplary/with_currency.json index ce7c92838fa..35698ed86b5 100644 --- a/adapters/visx/visxtest/exemplary/with_currency.json +++ b/adapters/visx/visxtest/exemplary/with_currency.json @@ -44,7 +44,8 @@ } } }] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/visx/visxtest/supplemental/bad_response.json b/adapters/visx/visxtest/supplemental/bad_response.json index 09376f942d2..21da96d8783 100644 --- a/adapters/visx/visxtest/supplemental/bad_response.json +++ b/adapters/visx/visxtest/supplemental/bad_response.json @@ -44,7 +44,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -55,7 +56,7 @@ "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type visx.visxResponse", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/visx/visxtest/supplemental/status_204.json b/adapters/visx/visxtest/supplemental/status_204.json index 7e0899d986b..b2d0b241c34 100644 --- a/adapters/visx/visxtest/supplemental/status_204.json +++ b/adapters/visx/visxtest/supplemental/status_204.json @@ -44,7 +44,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/visx/visxtest/supplemental/status_400.json b/adapters/visx/visxtest/supplemental/status_400.json index 82903ca938d..53325fe671e 100644 --- a/adapters/visx/visxtest/supplemental/status_400.json +++ b/adapters/visx/visxtest/supplemental/status_400.json @@ -44,7 +44,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/visx/visxtest/supplemental/status_418.json b/adapters/visx/visxtest/supplemental/status_418.json index 86ecf8b04a8..013697113db 100644 --- a/adapters/visx/visxtest/supplemental/status_418.json +++ b/adapters/visx/visxtest/supplemental/status_418.json @@ -44,7 +44,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 418, diff --git a/adapters/visx/visxtest/supplemental/wrong_imp_type.json b/adapters/visx/visxtest/supplemental/wrong_imp_type.json index 659a0204f6e..dd3ce10f141 100644 --- a/adapters/visx/visxtest/supplemental/wrong_imp_type.json +++ b/adapters/visx/visxtest/supplemental/wrong_imp_type.json @@ -38,7 +38,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/visx/visxtest/supplemental/wrong_impid.json b/adapters/visx/visxtest/supplemental/wrong_impid.json index ac18ac0719f..6052ac26254 100644 --- a/adapters/visx/visxtest/supplemental/wrong_impid.json +++ b/adapters/visx/visxtest/supplemental/wrong_impid.json @@ -46,7 +46,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/vox/params_test.go b/adapters/vox/params_test.go index be148d3b32d..536503555e8 100644 --- a/adapters/vox/params_test.go +++ b/adapters/vox/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/vox/vox.go b/adapters/vox/vox.go index 0b56fcbf9d7..3e5f03aa6ee 100644 --- a/adapters/vox/vox.go +++ b/adapters/vox/vox.go @@ -3,10 +3,12 @@ package vox import ( "encoding/json" "fmt" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { @@ -30,6 +32,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte Method: "POST", Uri: a.endpoint, Body: requestJSON, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), } return []*adapters.RequestData{requestData}, nil @@ -45,7 +48,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R } var response openrtb2.BidResponse - if err := json.Unmarshal(responseData.Body, &response); err != nil { + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { return nil, []error{err} } diff --git a/adapters/vox/vox_test.go b/adapters/vox/vox_test.go index 95d11a8ad79..dce89de3afb 100644 --- a/adapters/vox/vox_test.go +++ b/adapters/vox/vox_test.go @@ -3,9 +3,9 @@ package vox import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/vox/voxtest/exemplary/banner.json b/adapters/vox/voxtest/exemplary/banner.json index 898fcea4826..b9d990cbef3 100644 --- a/adapters/vox/voxtest/exemplary/banner.json +++ b/adapters/vox/voxtest/exemplary/banner.json @@ -42,7 +42,8 @@ } ], "cur": [ "USD" ] - } + }, + "impIDs":["8a7510f9-0ca1-44c4-a8c6-1ce639b5eef9"] }, "mockResponse": { "status": 200, diff --git a/adapters/vox/voxtest/exemplary/video.json b/adapters/vox/voxtest/exemplary/video.json index 37464da1aec..f0d553f2c84 100644 --- a/adapters/vox/voxtest/exemplary/video.json +++ b/adapters/vox/voxtest/exemplary/video.json @@ -49,7 +49,8 @@ } } }] - } + }, + "impIDs":["d190d6f3-5264-4df5-91b2-8a9c72cbeb6a"] }, "mockResponse": { diff --git a/adapters/vox/voxtest/supplemental/response_204_to_nocontent.json b/adapters/vox/voxtest/supplemental/response_204_to_nocontent.json index 0a49b85d612..62a31b5691b 100644 --- a/adapters/vox/voxtest/supplemental/response_204_to_nocontent.json +++ b/adapters/vox/voxtest/supplemental/response_204_to_nocontent.json @@ -31,7 +31,8 @@ } } }] - } + }, + "impIDs":["Impression id #1"] }, "mockResponse": { diff --git a/adapters/vox/voxtest/supplemental/response_500_to_error.json b/adapters/vox/voxtest/supplemental/response_500_to_error.json index f245ef45c99..53bfadaedea 100644 --- a/adapters/vox/voxtest/supplemental/response_500_to_error.json +++ b/adapters/vox/voxtest/supplemental/response_500_to_error.json @@ -37,7 +37,8 @@ } } }] - } + }, + "impIDs":["Impression id #4"] }, "mockResponse": { diff --git a/adapters/vrtcal/params_test.go b/adapters/vrtcal/params_test.go index d45d3b39013..8ba180db2b8 100644 --- a/adapters/vrtcal/params_test.go +++ b/adapters/vrtcal/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) //Vrtcal doesn't currently require any custom fields. This file is included for conformity only diff --git a/adapters/vrtcal/vrtcal.go b/adapters/vrtcal/vrtcal.go index ab47eddb441..e0df0bca954 100644 --- a/adapters/vrtcal/vrtcal.go +++ b/adapters/vrtcal/vrtcal.go @@ -5,11 +5,12 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type VrtcalAdapter struct { @@ -34,6 +35,7 @@ func (a *VrtcalAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adap Uri: a.endpoint, Body: reqJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), } adapterRequests = append(adapterRequests, &reqData) @@ -62,7 +64,7 @@ func (a *VrtcalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalR var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } diff --git a/adapters/vrtcal/vrtcal_test.go b/adapters/vrtcal/vrtcal_test.go index 31e6c78e2c1..59d29aaee31 100644 --- a/adapters/vrtcal/vrtcal_test.go +++ b/adapters/vrtcal/vrtcal_test.go @@ -3,9 +3,9 @@ package vrtcal import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/vrtcal/vrtcaltest/exemplary/simple-banner.json b/adapters/vrtcal/vrtcaltest/exemplary/simple-banner.json index 27682f7ee21..0f403c1557d 100644 --- a/adapters/vrtcal/vrtcaltest/exemplary/simple-banner.json +++ b/adapters/vrtcal/vrtcaltest/exemplary/simple-banner.json @@ -51,7 +51,8 @@ "app": { "id": "fake-app-id" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/vrtcal/vrtcaltest/exemplary/simple-native.json b/adapters/vrtcal/vrtcaltest/exemplary/simple-native.json index 3c63914c3a6..848631a780b 100644 --- a/adapters/vrtcal/vrtcaltest/exemplary/simple-native.json +++ b/adapters/vrtcal/vrtcaltest/exemplary/simple-native.json @@ -43,7 +43,8 @@ "app": { "id": "fake-app-id" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/vrtcal/vrtcaltest/exemplary/simple-video.json b/adapters/vrtcal/vrtcaltest/exemplary/simple-video.json index 2baaf1aa4ba..b2a94d5de51 100644 --- a/adapters/vrtcal/vrtcaltest/exemplary/simple-video.json +++ b/adapters/vrtcal/vrtcaltest/exemplary/simple-video.json @@ -47,7 +47,8 @@ "app": { "id": "fake-app-id" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/vrtcal/vrtcaltest/exemplary/web-simple-banner.json b/adapters/vrtcal/vrtcaltest/exemplary/web-simple-banner.json index eb7d2310d63..704b0afa5e3 100644 --- a/adapters/vrtcal/vrtcaltest/exemplary/web-simple-banner.json +++ b/adapters/vrtcal/vrtcaltest/exemplary/web-simple-banner.json @@ -53,7 +53,8 @@ "id": "fake-site-id", "page": "http://www.vrtcal.com" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/vrtcal/vrtcaltest/exemplary/web-simple-native.json b/adapters/vrtcal/vrtcaltest/exemplary/web-simple-native.json index 6b416ab0726..4790d0c6730 100644 --- a/adapters/vrtcal/vrtcaltest/exemplary/web-simple-native.json +++ b/adapters/vrtcal/vrtcaltest/exemplary/web-simple-native.json @@ -44,7 +44,8 @@ "id": "fake-site-id", "page": "http://www.vrtcal.com" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/vrtcal/vrtcaltest/exemplary/web-simple-video.json b/adapters/vrtcal/vrtcaltest/exemplary/web-simple-video.json index 96ffd7099d1..48ed6d7bbad 100644 --- a/adapters/vrtcal/vrtcaltest/exemplary/web-simple-video.json +++ b/adapters/vrtcal/vrtcaltest/exemplary/web-simple-video.json @@ -49,7 +49,8 @@ "id": "fake-site-id", "page": "http://www.vrtcal.com" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/vrtcal/vrtcaltest/supplemental/unsupported_return_type.json b/adapters/vrtcal/vrtcaltest/supplemental/unsupported_return_type.json index e2fc18f373e..2f6e0fed01e 100644 --- a/adapters/vrtcal/vrtcaltest/supplemental/unsupported_return_type.json +++ b/adapters/vrtcal/vrtcaltest/supplemental/unsupported_return_type.json @@ -51,7 +51,8 @@ "app": { "id": "fake-app-id" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/liftoff/param_test.go b/adapters/vungle/param_test.go similarity index 78% rename from adapters/liftoff/param_test.go rename to adapters/vungle/param_test.go index d7cd5d73c09..7053669edf4 100644 --- a/adapters/liftoff/param_test.go +++ b/adapters/vungle/param_test.go @@ -1,10 +1,10 @@ -package liftoff +package vungle import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) var validParams = []string{ @@ -26,7 +26,7 @@ func TestValidParams(t *testing.T) { } for _, p := range validParams { - if err := validator.Validate(openrtb_ext.BidderLiftoff, json.RawMessage(p)); err != nil { + if err := validator.Validate(openrtb_ext.BidderVungle, json.RawMessage(p)); err != nil { t.Errorf("Schema rejected valid params: %s", p) } } @@ -39,7 +39,7 @@ func TestInvalidParams(t *testing.T) { } for _, p := range invalidParams { - if err := validator.Validate(openrtb_ext.BidderLiftoff, json.RawMessage(p)); err == nil { + if err := validator.Validate(openrtb_ext.BidderVungle, json.RawMessage(p)); err == nil { t.Errorf("Schema allowed invalid params: %s", p) } } diff --git a/adapters/liftoff/liftoff.go b/adapters/vungle/vungle.go similarity index 69% rename from adapters/liftoff/liftoff.go rename to adapters/vungle/vungle.go index b649da6f631..6e6967fc227 100644 --- a/adapters/liftoff/liftoff.go +++ b/adapters/vungle/vungle.go @@ -1,4 +1,4 @@ -package liftoff +package vungle import ( "encoding/json" @@ -7,10 +7,11 @@ import ( "net/http" "strings" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) const SupportedCurrency = "USD" @@ -19,18 +20,18 @@ type adapter struct { Endpoint string } -type liftoffImpressionExt struct { +type vungleImpressionExt struct { *adapters.ExtImpBidder // Ext represents the vungle extension. - Ext openrtb_ext.ImpExtLiftoff `json:"vungle"` + Ext openrtb_ext.ImpExtVungle `json:"vungle"` } -// Builder builds a new instance of the Liftoff adapter for the given bidder with the given config. +// Builder builds a new instance of the Vungle adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { return &adapter{Endpoint: config.Endpoint}, nil } -// MakeRequests split impressions into bid requests and change them into the form that liftoff can handle. +// MakeRequests split impressions into bid requests and change them into the form that vungle can handle. func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var requests []*adapters.RequestData var errs []error @@ -51,15 +52,15 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte imp.BidFloor = convertedValue } - var impExt liftoffImpressionExt - if err := json.Unmarshal(imp.Ext, &impExt); err != nil { + var impExt vungleImpressionExt + if err := jsonutil.Unmarshal(imp.Ext, &impExt); err != nil { errs = append(errs, fmt.Errorf("failed unmarshalling imp ext (err)%s", err.Error())) continue } // get placement_reference_id & pub_app_store_id - var bidderImpExt openrtb_ext.ImpExtLiftoff - if err := json.Unmarshal(impExt.Bidder, &bidderImpExt); err != nil { + var bidderImpExt openrtb_ext.ImpExtVungle + if err := jsonutil.Unmarshal(impExt.Bidder, &bidderImpExt); err != nil { errs = append(errs, fmt.Errorf("failed unmarshalling bidder imp ext (err)%s", err.Error())) continue } @@ -76,10 +77,22 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte imp.TagID = bidderImpExt.PlacementRefID requestCopy.Imp = []openrtb2.Imp{imp} // must make a shallow copy for pointers. - requestAppCopy := *request.App - requestAppCopy.ID = bidderImpExt.PubAppStoreID - requestCopy.App = &requestAppCopy + // If it is site object, need to construct an app with pub_store_id. + var requestAppCopy openrtb2.App + if request.App != nil { + requestAppCopy = *request.App + requestAppCopy.ID = bidderImpExt.PubAppStoreID + } else if request.Site != nil { + requestCopy.Site = nil + requestAppCopy = openrtb2.App{ + ID: bidderImpExt.PubAppStoreID, + } + } else { + errs = append(errs, errors.New("failed constructing app, must have app or site object in bid request")) + continue + } + requestCopy.App = &requestAppCopy requestJSON, err := json.Marshal(requestCopy) if err != nil { errs = append(errs, err) @@ -95,6 +108,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte "Accept": []string{"application/json"}, "X-OpenRTB-Version": []string{"2.5"}, }, + ImpIDs: openrtb_ext.GetImpIDs(requestCopy.Imp), } requests = append(requests, requestData) @@ -103,7 +117,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte return requests, errs } -// MakeBids collect bid response from liftoff and change them into the form that Prebid Server can handle. +// MakeBids collect bid response from vungle and change them into the form that Prebid Server can handle. func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { if adapters.IsResponseStatusCodeNoContent(responseData) { return nil, nil @@ -114,7 +128,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R } var response openrtb2.BidResponse - if err := json.Unmarshal(responseData.Body, &response); err != nil { + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { return nil, []error{err} } @@ -126,6 +140,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R b := &adapters.TypedBid{ Bid: &seatBid.Bid[i], BidType: openrtb_ext.BidTypeVideo, + Seat: openrtb_ext.BidderName(seatBid.Seat), } bidResponse.Bids = append(bidResponse.Bids, b) diff --git a/adapters/vungle/vungle_test.go b/adapters/vungle/vungle_test.go new file mode 100644 index 00000000000..740208dbe0c --- /dev/null +++ b/adapters/vungle/vungle_test.go @@ -0,0 +1,21 @@ +package vungle + +import ( + "testing" + + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + conf := config.Adapter{ + Endpoint: "https://vungle.io/bit/t", + } + bidder, buildErr := Builder(openrtb_ext.BidderVungle, conf, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 667, DataCenter: "2"}) + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "vungletest", bidder) +} diff --git a/adapters/liftoff/liftofftest/exemplary/app_video_instl.json b/adapters/vungle/vungletest/exemplary/app_video_instl.json similarity index 95% rename from adapters/liftoff/liftofftest/exemplary/app_video_instl.json rename to adapters/vungle/vungletest/exemplary/app_video_instl.json index 12a656b0be8..a0766528bbe 100644 --- a/adapters/liftoff/liftofftest/exemplary/app_video_instl.json +++ b/adapters/vungle/vungletest/exemplary/app_video_instl.json @@ -45,7 +45,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://liftoff.io/bit/t", + "uri": "https://vungle.io/bit/t", "headers": { "Content-Type": ["application/json"], "Accept": ["application/json"], @@ -101,7 +101,8 @@ "debug": true } } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -144,7 +145,8 @@ "w": 300, "h": 250 }, - "type": "video" + "type": "video", + "seat": "seat-id" } ] } diff --git a/adapters/liftoff/liftofftest/exemplary/app_video_rewarded.json b/adapters/vungle/vungletest/exemplary/app_video_rewarded.json similarity index 95% rename from adapters/liftoff/liftofftest/exemplary/app_video_rewarded.json rename to adapters/vungle/vungletest/exemplary/app_video_rewarded.json index 59c78e12838..5e86fe9a61a 100644 --- a/adapters/liftoff/liftofftest/exemplary/app_video_rewarded.json +++ b/adapters/vungle/vungletest/exemplary/app_video_rewarded.json @@ -42,7 +42,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://liftoff.io/bit/t", + "uri": "https://vungle.io/bit/t", "headers": { "Content-Type": ["application/json"], "Accept": ["application/json"], @@ -94,7 +94,8 @@ "user": { "buyeruid": "123" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -137,7 +138,8 @@ "w": 300, "h": 250 }, - "type": "video" + "type": "video", + "seat": "seat-id" } ] } diff --git a/adapters/vungle/vungletest/exemplary/site_video_instl.json b/adapters/vungle/vungletest/exemplary/site_video_instl.json new file mode 100644 index 00000000000..56a8deb8520 --- /dev/null +++ b/adapters/vungle/vungletest/exemplary/site_video_instl.json @@ -0,0 +1,153 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "name": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "instl": 1, + "ext": { + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + }, + "ext": { + "prebid": { + "debug": true + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://vungle.io/bit/t", + "headers": { + "Content-Type": ["application/json"], + "Accept": ["application/json"], + "X-OpenRTB-Version": ["2.5"] + }, + "body": { + "id": "test-request-id", + "app": { + "id": "123" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "123", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "instl": 1, + "ext": { + "prebid": null, + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + }, + "vungle": { + "bid_token": "123", + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + }, + "ext": { + "prebid": { + "debug": true + } + } + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 300, + "h": 250 + }, + "type": "video", + "seat": "seat-id" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/vungle/vungletest/exemplary/site_video_rewarded.json b/adapters/vungle/vungletest/exemplary/site_video_rewarded.json new file mode 100644 index 00000000000..9388c5be22a --- /dev/null +++ b/adapters/vungle/vungletest/exemplary/site_video_rewarded.json @@ -0,0 +1,146 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "name": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "prebid": { + "is_rewarded_inventory": 1 + }, + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://vungle.io/bit/t", + "headers": { + "Content-Type": ["application/json"], + "Accept": ["application/json"], + "X-OpenRTB-Version": ["2.5"] + }, + "body": { + "id": "test-request-id", + "app": { + "id": "123" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "123", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "prebid": { + "is_rewarded_inventory": 1 + }, + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + }, + "vungle": { + "bid_token": "123", + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + } + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 300, + "h": 250 + }, + "type": "video", + "seat": "seat-id" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/liftoff/liftofftest/supplemental/appid_placementid_check.json b/adapters/vungle/vungletest/supplemental/appid_placementid_check.json similarity index 95% rename from adapters/liftoff/liftofftest/supplemental/appid_placementid_check.json rename to adapters/vungle/vungletest/supplemental/appid_placementid_check.json index 59c78e12838..5e86fe9a61a 100644 --- a/adapters/liftoff/liftofftest/supplemental/appid_placementid_check.json +++ b/adapters/vungle/vungletest/supplemental/appid_placementid_check.json @@ -42,7 +42,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://liftoff.io/bit/t", + "uri": "https://vungle.io/bit/t", "headers": { "Content-Type": ["application/json"], "Accept": ["application/json"], @@ -94,7 +94,8 @@ "user": { "buyeruid": "123" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -137,7 +138,8 @@ "w": 300, "h": 250 }, - "type": "video" + "type": "video", + "seat": "seat-id" } ] } diff --git a/adapters/liftoff/liftofftest/supplemental/missing_appid_or_placementid.json b/adapters/vungle/vungletest/supplemental/missing_appid_or_placementid.json similarity index 96% rename from adapters/liftoff/liftofftest/supplemental/missing_appid_or_placementid.json rename to adapters/vungle/vungletest/supplemental/missing_appid_or_placementid.json index 6095d9c5168..4eda9800645 100644 --- a/adapters/liftoff/liftofftest/supplemental/missing_appid_or_placementid.json +++ b/adapters/vungle/vungletest/supplemental/missing_appid_or_placementid.json @@ -41,7 +41,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://liftoff.io/bit/t", + "uri": "https://vungle.io/bit/t", "headers": { "Content-Type": ["application/json"], "Accept": ["application/json"], @@ -91,7 +91,8 @@ "user": { "buyeruid": "123" } - } + }, + "impIDs":["test-imp-id"] } } ], diff --git a/adapters/vungle/vungletest/supplemental/no_site_or_app_video_rewarded.json b/adapters/vungle/vungletest/supplemental/no_site_or_app_video_rewarded.json new file mode 100644 index 00000000000..ec846f1375c --- /dev/null +++ b/adapters/vungle/vungletest/supplemental/no_site_or_app_video_rewarded.json @@ -0,0 +1,48 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "prebid": { + "is_rewarded_inventory": 1 + }, + "bidder": { + "app_store_id": "123", + "placement_reference_id": "123" + } + } + } + ], + "user": { + "buyeruid": "123" + } + }, + "httpCalls": [], + "expectedBidResponses": [], + "expectedMakeRequestsErrors": [ + { + "value": "failed constructing app, must have app or site object in bid request", + "comparison": "literal" + } + ], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/liftoff/liftofftest/supplemental/response_code_204.json b/adapters/vungle/vungletest/supplemental/response_code_204.json similarity index 96% rename from adapters/liftoff/liftofftest/supplemental/response_code_204.json rename to adapters/vungle/vungletest/supplemental/response_code_204.json index 4abefffc5c9..16a4dbc66ac 100644 --- a/adapters/liftoff/liftofftest/supplemental/response_code_204.json +++ b/adapters/vungle/vungletest/supplemental/response_code_204.json @@ -39,7 +39,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://liftoff.io/bit/t", + "uri": "https://vungle.io/bit/t", "headers": { "Content-Type": ["application/json"], "Accept": ["application/json"], @@ -89,7 +89,8 @@ "user": { "buyeruid": "123" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/liftoff/liftofftest/supplemental/response_code_400.json b/adapters/vungle/vungletest/supplemental/response_code_400.json similarity index 96% rename from adapters/liftoff/liftofftest/supplemental/response_code_400.json rename to adapters/vungle/vungletest/supplemental/response_code_400.json index de5b0db421e..6975362ed18 100644 --- a/adapters/liftoff/liftofftest/supplemental/response_code_400.json +++ b/adapters/vungle/vungletest/supplemental/response_code_400.json @@ -39,7 +39,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://liftoff.io/bit/t", + "uri": "https://vungle.io/bit/t", "headers": { "Content-Type": ["application/json"], "Accept": ["application/json"], @@ -89,7 +89,8 @@ "user": { "buyeruid": "123" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/liftoff/liftofftest/supplemental/response_code_non_200.json b/adapters/vungle/vungletest/supplemental/response_code_non_200.json similarity index 96% rename from adapters/liftoff/liftofftest/supplemental/response_code_non_200.json rename to adapters/vungle/vungletest/supplemental/response_code_non_200.json index 17e1730ac2c..3582a4b245c 100644 --- a/adapters/liftoff/liftofftest/supplemental/response_code_non_200.json +++ b/adapters/vungle/vungletest/supplemental/response_code_non_200.json @@ -39,7 +39,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://liftoff.io/bit/t", + "uri": "https://vungle.io/bit/t", "headers": { "Content-Type": ["application/json"], "Accept": ["application/json"], @@ -89,7 +89,8 @@ "user": { "buyeruid": "123" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 403, diff --git a/adapters/liftoff/liftofftest/supplemental/liftoff_ext_check.json b/adapters/vungle/vungletest/supplemental/vungle_ext_check.json similarity index 95% rename from adapters/liftoff/liftofftest/supplemental/liftoff_ext_check.json rename to adapters/vungle/vungletest/supplemental/vungle_ext_check.json index de936f028e3..c2c179734e5 100644 --- a/adapters/liftoff/liftofftest/supplemental/liftoff_ext_check.json +++ b/adapters/vungle/vungletest/supplemental/vungle_ext_check.json @@ -39,7 +39,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://liftoff.io/bit/t", + "uri": "https://vungle.io/bit/t", "headers": { "Content-Type": ["application/json"], "Accept": ["application/json"], @@ -89,7 +89,8 @@ "user": { "buyeruid": "123" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -132,7 +133,8 @@ "h": 250, "w": 300 }, - "type": "video" + "type": "video", + "seat": "seat-id" } ] } diff --git a/adapters/xeworks/params_test.go b/adapters/xeworks/params_test.go index 68d36096049..0ce8d99f48f 100644 --- a/adapters/xeworks/params_test.go +++ b/adapters/xeworks/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/xeworks/xeworks.go b/adapters/xeworks/xeworks.go index 35e551b1034..bd9d1795283 100644 --- a/adapters/xeworks/xeworks.go +++ b/adapters/xeworks/xeworks.go @@ -6,12 +6,13 @@ import ( "net/http" "text/template" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type bidType struct { @@ -41,14 +42,14 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co func (a *adapter) buildEndpointFromRequest(imp *openrtb2.Imp) (string, error) { var impExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &impExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &impExt); err != nil { return "", &errortypes.BadInput{ Message: fmt.Sprintf("Failed to deserialize bidder impression extension: %v", err), } } var xeworksExt openrtb_ext.ExtXeworks - if err := json.Unmarshal(impExt.Bidder, &xeworksExt); err != nil { + if err := jsonutil.Unmarshal(impExt.Bidder, &xeworksExt); err != nil { return "", &errortypes.BadInput{ Message: fmt.Sprintf("Failed to deserialize Xeworks extension: %v", err), } @@ -91,6 +92,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte Body: requestJSON, Uri: endpoint, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(requestCopy.Imp), } requests = append(requests, request) @@ -115,7 +117,7 @@ func (a *adapter) MakeBids(openRTBRequest *openrtb2.BidRequest, requestToBidder } var bidResp openrtb2.BidResponse - if err := json.Unmarshal(bidderRawResponse.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(bidderRawResponse.Body, &bidResp); err != nil { return nil, []error{err} } @@ -135,7 +137,7 @@ func prepareBidResponse(seats []openrtb2.SeatBid) (*adapters.BidderResponse, []e for _, seatBid := range seats { for bidId, bid := range seatBid.Bid { var bidExt bidExt - if err := json.Unmarshal(bid.Ext, &bidExt); err != nil { + if err := jsonutil.Unmarshal(bid.Ext, &bidExt); err != nil { errs = append(errs, &errortypes.BadServerResponse{ Message: fmt.Sprintf("Failed to parse Bid[%d].Ext: %s", bidId, err.Error()), }) diff --git a/adapters/xeworks/xeworks_test.go b/adapters/xeworks/xeworks_test.go index 4869a05a229..842045af1d9 100644 --- a/adapters/xeworks/xeworks_test.go +++ b/adapters/xeworks/xeworks_test.go @@ -3,9 +3,9 @@ package xeworks import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/xeworks/xeworkstest/exemplary/banner.json b/adapters/xeworks/xeworkstest/exemplary/banner.json index 657c36c6d58..9341244a104 100644 --- a/adapters/xeworks/xeworkstest/exemplary/banner.json +++ b/adapters/xeworks/xeworkstest/exemplary/banner.json @@ -103,7 +103,8 @@ "id": "pubid" } } - } + }, + "impIDs":["1"] }, "mockResponse": { "status": 200, @@ -183,7 +184,8 @@ "id": "pubid" } } - } + }, + "impIDs":["2"] }, "mockResponse": { "status": 200, diff --git a/adapters/xeworks/xeworkstest/exemplary/native.json b/adapters/xeworks/xeworkstest/exemplary/native.json index 9c8eb6a1dbc..137f0d2befb 100644 --- a/adapters/xeworks/xeworkstest/exemplary/native.json +++ b/adapters/xeworks/xeworkstest/exemplary/native.json @@ -88,7 +88,8 @@ "id": "pubid" } } - } + }, + "impIDs":["id"] }, "mockResponse": { "status": 200, diff --git a/adapters/xeworks/xeworkstest/exemplary/video.json b/adapters/xeworks/xeworkstest/exemplary/video.json index 5f509baad32..730104a22b7 100644 --- a/adapters/xeworks/xeworkstest/exemplary/video.json +++ b/adapters/xeworks/xeworkstest/exemplary/video.json @@ -128,7 +128,8 @@ "id": "pubid" } } - } + }, + "impIDs":["id"] }, "mockResponse": { "status": 200, diff --git a/adapters/xeworks/xeworkstest/supplemental/bad-response.json b/adapters/xeworks/xeworkstest/supplemental/bad-response.json index 4f8b9bd2d79..ecfa343be36 100644 --- a/adapters/xeworks/xeworkstest/supplemental/bad-response.json +++ b/adapters/xeworks/xeworkstest/supplemental/bad-response.json @@ -88,7 +88,8 @@ "id": "pubid" } } - } + }, + "impIDs":["id"] }, "mockResponse": { "status": 200, @@ -98,7 +99,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "value": "expect { or n, but found \"", "comparison": "literal" } ], diff --git a/adapters/xeworks/xeworkstest/supplemental/empty-mediatype.json b/adapters/xeworks/xeworkstest/supplemental/empty-mediatype.json index c2d62d09c11..cd5dab395cb 100644 --- a/adapters/xeworks/xeworkstest/supplemental/empty-mediatype.json +++ b/adapters/xeworks/xeworkstest/supplemental/empty-mediatype.json @@ -88,7 +88,8 @@ "id": "pubid" } } - } + }, + "impIDs":["id"] }, "mockResponse": { "status": 200, diff --git a/adapters/xeworks/xeworkstest/supplemental/empty-seatbid-0-bid.json b/adapters/xeworks/xeworkstest/supplemental/empty-seatbid-0-bid.json index 98bcbbeb8df..e37da8ccdde 100644 --- a/adapters/xeworks/xeworkstest/supplemental/empty-seatbid-0-bid.json +++ b/adapters/xeworks/xeworkstest/supplemental/empty-seatbid-0-bid.json @@ -88,7 +88,8 @@ "id": "pubid" } } - } + }, + "impIDs":["id"] }, "mockResponse": { "status": 200, diff --git a/adapters/xeworks/xeworkstest/supplemental/empty-seatbid.json b/adapters/xeworks/xeworkstest/supplemental/empty-seatbid.json index d522e8ce292..eb1737517b0 100644 --- a/adapters/xeworks/xeworkstest/supplemental/empty-seatbid.json +++ b/adapters/xeworks/xeworkstest/supplemental/empty-seatbid.json @@ -88,7 +88,8 @@ "id": "pubid" } } - } + }, + "impIDs":["id"] }, "mockResponse": { "status": 200, diff --git a/adapters/xeworks/xeworkstest/supplemental/invalid-ext-bidder-object.json b/adapters/xeworks/xeworkstest/supplemental/invalid-ext-bidder-object.json index fe7fd9c8b19..77347bfd3d7 100644 --- a/adapters/xeworks/xeworkstest/supplemental/invalid-ext-bidder-object.json +++ b/adapters/xeworks/xeworkstest/supplemental/invalid-ext-bidder-object.json @@ -42,7 +42,7 @@ "httpCalls": [], "expectedMakeRequestsErrors": [ { - "value": "Failed to deserialize Xeworks extension: json: cannot unmarshal array into Go value of type openrtb_ext.ExtXeworks", + "value": "Failed to deserialize Xeworks extension: expect { or n, but found [", "comparison": "literal" } ] diff --git a/adapters/xeworks/xeworkstest/supplemental/invalid-ext-object.json b/adapters/xeworks/xeworkstest/supplemental/invalid-ext-object.json index aa215eb3e34..16cf58bbe54 100644 --- a/adapters/xeworks/xeworkstest/supplemental/invalid-ext-object.json +++ b/adapters/xeworks/xeworkstest/supplemental/invalid-ext-object.json @@ -40,7 +40,7 @@ "httpCalls": [], "expectedMakeRequestsErrors": [ { - "value": "Failed to deserialize bidder impression extension: json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "value": "Failed to deserialize bidder impression extension: expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/xeworks/xeworkstest/supplemental/invalid-mediatype.json b/adapters/xeworks/xeworkstest/supplemental/invalid-mediatype.json index 39e0cd236e0..aa61af551a0 100644 --- a/adapters/xeworks/xeworkstest/supplemental/invalid-mediatype.json +++ b/adapters/xeworks/xeworkstest/supplemental/invalid-mediatype.json @@ -88,7 +88,8 @@ "id": "pubid" } } - } + }, + "impIDs":["id"] }, "mockResponse": { "status": 200, diff --git a/adapters/xeworks/xeworkstest/supplemental/status-204.json b/adapters/xeworks/xeworkstest/supplemental/status-204.json index 72dbf94dfc3..9252b90fd5e 100644 --- a/adapters/xeworks/xeworkstest/supplemental/status-204.json +++ b/adapters/xeworks/xeworkstest/supplemental/status-204.json @@ -88,7 +88,8 @@ "id": "pubid" } } - } + }, + "impIDs":["id"] }, "mockResponse": { "status": 204, diff --git a/adapters/xeworks/xeworkstest/supplemental/status-400.json b/adapters/xeworks/xeworkstest/supplemental/status-400.json index 220746084e6..614a9897e18 100644 --- a/adapters/xeworks/xeworkstest/supplemental/status-400.json +++ b/adapters/xeworks/xeworkstest/supplemental/status-400.json @@ -88,7 +88,8 @@ "id": "pubid" } } - } + }, + "impIDs":["id"] }, "mockResponse": { "status": 400, diff --git a/adapters/xeworks/xeworkstest/supplemental/status-503.json b/adapters/xeworks/xeworkstest/supplemental/status-503.json index 00b24dc97b0..dcffcacde5d 100644 --- a/adapters/xeworks/xeworkstest/supplemental/status-503.json +++ b/adapters/xeworks/xeworkstest/supplemental/status-503.json @@ -88,7 +88,8 @@ "id": "pubid" } } - } + }, + "impIDs":["id"] }, "mockResponse": { "status": 503 diff --git a/adapters/xeworks/xeworkstest/supplemental/unexpected-status.json b/adapters/xeworks/xeworkstest/supplemental/unexpected-status.json index fd1b4f9d3f0..553d4bdfa68 100644 --- a/adapters/xeworks/xeworkstest/supplemental/unexpected-status.json +++ b/adapters/xeworks/xeworkstest/supplemental/unexpected-status.json @@ -88,7 +88,8 @@ "id": "pubid" } } - } + }, + "impIDs":["id"] }, "mockResponse": { "status": 403, diff --git a/adapters/yahooAds/params_test.go b/adapters/yahooAds/params_test.go index c0deaaa32c9..42a5c16ae75 100644 --- a/adapters/yahooAds/params_test.go +++ b/adapters/yahooAds/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // This file actually intends to test static/bidder-params/yahooAds.json diff --git a/adapters/yahooAds/yahooAds.go b/adapters/yahooAds/yahooAds.go index 3597d0e359c..4a03bc3e7fc 100644 --- a/adapters/yahooAds/yahooAds.go +++ b/adapters/yahooAds/yahooAds.go @@ -5,11 +5,13 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" ) type adapter struct { @@ -32,7 +34,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E for idx, imp := range request.Imp { var bidderExt adapters.ExtImpBidder - err := json.Unmarshal(imp.Ext, &bidderExt) + err := jsonutil.Unmarshal(imp.Ext, &bidderExt) if err != nil { err = &errortypes.BadInput{ Message: fmt.Sprintf("imp #%d: ext.bidder not provided", idx), @@ -42,7 +44,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E } var yahooAdsExt openrtb_ext.ExtImpYahooAds - err = json.Unmarshal(bidderExt.Bidder, &yahooAdsExt) + err = jsonutil.Unmarshal(bidderExt.Bidder, &yahooAdsExt) if err != nil { err = &errortypes.BadInput{ Message: fmt.Sprintf("imp #%d: %s", idx, err.Error()), @@ -80,6 +82,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E Uri: a.URI, Body: reqJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(reqCopy.Imp), }) } @@ -99,7 +102,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest } var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("Bad server response: %d.", err), }} @@ -175,12 +178,14 @@ func changeRequestForBidService(request *openrtb2.BidRequest, extension *openrtb requestRegs.Ext = json.RawMessage("{}") } var regsExt map[string]json.RawMessage - err := json.Unmarshal(requestRegs.Ext, ®sExt) + err := jsonutil.Unmarshal(requestRegs.Ext, ®sExt) if err != nil { return err } regsExt["gpp"], err = json.Marshal(&requestRegs.GPP) - + if err != nil { + return fmt.Errorf("failed to marshal requestRegs.GPP: %v", err) + } if requestRegs.GPPSID != nil { regsExt["gpp_sid"], err = json.Marshal(&requestRegs.GPPSID) if err != nil { @@ -212,8 +217,8 @@ func validateBanner(banner *openrtb2.Banner) error { return fmt.Errorf("No sizes provided for Banner %v", banner.Format) } - banner.W = openrtb2.Int64Ptr(banner.Format[0].W) - banner.H = openrtb2.Int64Ptr(banner.Format[0].H) + banner.W = ptrutil.ToPtr(banner.Format[0].W) + banner.H = ptrutil.ToPtr(banner.Format[0].H) return nil } diff --git a/adapters/yahooAds/yahooAds_test.go b/adapters/yahooAds/yahooAds_test.go index 924eabd5ec1..0eaf711faf3 100644 --- a/adapters/yahooAds/yahooAds_test.go +++ b/adapters/yahooAds/yahooAds_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestYahooAdsBidderEndpointConfig(t *testing.T) { diff --git a/adapters/yahooAds/yahooAdstest/exemplary/simple-app-banner.json b/adapters/yahooAds/yahooAdstest/exemplary/simple-app-banner.json index 7ad41161915..6b61861d9b7 100644 --- a/adapters/yahooAds/yahooAdstest/exemplary/simple-app-banner.json +++ b/adapters/yahooAds/yahooAdstest/exemplary/simple-app-banner.json @@ -69,7 +69,8 @@ "app": { "id": "dcn1" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yahooAds/yahooAdstest/exemplary/simple-banner.json b/adapters/yahooAds/yahooAdstest/exemplary/simple-banner.json index 7036664d4ad..3d0b4a37b0d 100644 --- a/adapters/yahooAds/yahooAdstest/exemplary/simple-banner.json +++ b/adapters/yahooAds/yahooAdstest/exemplary/simple-banner.json @@ -69,7 +69,8 @@ "site": { "id": "dcn1" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yahooAds/yahooAdstest/exemplary/simple-video.json b/adapters/yahooAds/yahooAdstest/exemplary/simple-video.json index ebf7af93d53..9f9d188d001 100644 --- a/adapters/yahooAds/yahooAdstest/exemplary/simple-video.json +++ b/adapters/yahooAds/yahooAdstest/exemplary/simple-video.json @@ -84,7 +84,8 @@ "id": "1" } } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yahooAds/yahooAdstest/supplemental/non-supported-requests-bids-ignored.json b/adapters/yahooAds/yahooAdstest/supplemental/non-supported-requests-bids-ignored.json index c0d77fa496b..5e0820761d6 100644 --- a/adapters/yahooAds/yahooAdstest/supplemental/non-supported-requests-bids-ignored.json +++ b/adapters/yahooAds/yahooAdstest/supplemental/non-supported-requests-bids-ignored.json @@ -66,7 +66,8 @@ "site": { "id": "dcn1" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yahooAds/yahooAdstest/supplemental/server-error.json b/adapters/yahooAds/yahooAdstest/supplemental/server-error.json index e933d9e589e..15da2afe3b0 100644 --- a/adapters/yahooAds/yahooAdstest/supplemental/server-error.json +++ b/adapters/yahooAds/yahooAdstest/supplemental/server-error.json @@ -54,7 +54,8 @@ "site": { "id": "dcn1" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 500, diff --git a/adapters/yahooAds/yahooAdstest/supplemental/server-response-wrong-impid.json b/adapters/yahooAds/yahooAdstest/supplemental/server-response-wrong-impid.json index f40819497a8..a831dba64b2 100644 --- a/adapters/yahooAds/yahooAdstest/supplemental/server-response-wrong-impid.json +++ b/adapters/yahooAds/yahooAdstest/supplemental/server-response-wrong-impid.json @@ -68,7 +68,8 @@ "site": { "id": "dcn1" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp-overwrite.json b/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp-overwrite.json index 94c895b996d..950796692a4 100644 --- a/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp-overwrite.json +++ b/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp-overwrite.json @@ -84,7 +84,8 @@ "param1": "val1" } } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp.json b/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp.json index 3d5aff6c531..73012144fb2 100644 --- a/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp.json +++ b/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp.json @@ -79,7 +79,8 @@ "gpp_sid": [6] } } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-ignore-width-when-height-missing.json b/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-ignore-width-when-height-missing.json index 1206005970c..513e37f9f14 100644 --- a/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-ignore-width-when-height-missing.json +++ b/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-ignore-width-when-height-missing.json @@ -70,7 +70,8 @@ "site": { "id": "dcn1" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yandex/params_test.go b/adapters/yandex/params_test.go new file mode 100644 index 00000000000..0805be19889 --- /dev/null +++ b/adapters/yandex/params_test.go @@ -0,0 +1,87 @@ +package yandex + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderYandex, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderYandex, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"page_id": 123123, "imp_id": 123}`, + `{"placement_id": "A-123123-123"}`, + `{"placement_id": "B-A-123123-123"}`, + `{"placement_id": "123123-123"}`, +} + +var invalidParams = []string{ + `{"pageId": 123123, "impId": 123}`, + `{"page_id": 0, "imp_id": 0}`, + `{"page_id": "123123", "imp_id": "123"}`, + `{"page_id": "123123", "imp_id": "123", "placement_id": "123123"}`, + `{"page_id": "123123"}`, + `{"imp_id": "123"}`, + `{"placement_id": 123123}`, + `{"placement_id": "123123"}`, + `{"placement_id": "A-123123"}`, + `{"placement_id": "B-A-123123"}`, + `{}`, +} + +func TestValidPlacementIdMapper(t *testing.T) { + for ext, expectedPlacementId := range validPlacementIds { + val, err := mapExtToPlacementID(ext) + + assert.Equal(t, &expectedPlacementId, val) + assert.NoError(t, err) + } +} + +func TestInvalidPlacementIdMapper(t *testing.T) { + for _, ext := range invalidPlacementIds { + _, err := mapExtToPlacementID(ext) + + assert.Error(t, err) + } +} + +var validPlacementIds = map[openrtb_ext.ExtImpYandex]yandexPlacementID{ + {PlacementID: "A-12345-1"}: {PageID: "12345", ImpID: "1"}, + {PlacementID: "B-A-123123-123"}: {PageID: "123123", ImpID: "123"}, + {PlacementID: "111-222"}: {PageID: "111", ImpID: "222"}, + {PageID: 111, ImpID: 222}: {PageID: "111", ImpID: "222"}, +} + +var invalidPlacementIds = []openrtb_ext.ExtImpYandex{ + {PlacementID: "123123"}, + {PlacementID: "A-123123"}, + {PlacementID: "B-A-123123"}, + {PlacementID: "C-B-A-123123"}, +} diff --git a/adapters/yandex/yandex.go b/adapters/yandex/yandex.go new file mode 100644 index 00000000000..5a017c2bd65 --- /dev/null +++ b/adapters/yandex/yandex.go @@ -0,0 +1,341 @@ +package yandex + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + "text/template" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +const ( + refererQueryKey = "target-ref" + currencyQueryKey = "ssp-cur" + impIdQueryKey = "imp-id" +) + +// Composite id of an ad placement +type yandexPlacementID struct { + PageID string + ImpID string +} + +type adapter struct { + endpoint *template.Template +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + template, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } + + bidder := &adapter{ + endpoint: template, + } + + return bidder, nil +} + +func (a *adapter) MakeRequests(requestData *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var ( + requests []*adapters.RequestData + errors []error + ) + + referer := getReferer(requestData) + currency := getCurrency(requestData) + + for i := range requestData.Imp { + imp := requestData.Imp[i] + + placementId, err := getYandexPlacementId(imp) + if err != nil { + errors = append(errors, err) + continue + } + + if err := modifyImp(&imp); err != nil { + errors = append(errors, err) + continue + } + + resolvedUrl, err := a.resolveUrl(*placementId, referer, currency) + if err != nil { + errors = append(errors, err) + continue + } + + splittedRequestData := splitRequestDataByImp(requestData, imp) + + requestBody, err := json.Marshal(splittedRequestData) + if err != nil { + errors = append(errors, err) + continue + } + + requests = append(requests, &adapters.RequestData{ + Method: "POST", + Uri: resolvedUrl, + Body: requestBody, + Headers: getHeaders(&splittedRequestData), + ImpIDs: openrtb_ext.GetImpIDs(splittedRequestData.Imp), + }) + } + + return requests, errors +} + +func getHeaders(request *openrtb2.BidRequest) http.Header { + headers := http.Header{} + + if request.Device != nil && request.Site != nil { + addNonEmptyHeaders(&headers, map[string]string{ + "Referer": request.Site.Page, + "Accept-Language": request.Device.Language, + "User-Agent": request.Device.UA, + "X-Forwarded-For": request.Device.IP, + "X-Real-Ip": request.Device.IP, + "Content-Type": "application/json;charset=utf-8", + "Accept": "application/json", + }) + } + + return headers +} + +func addNonEmptyHeaders(headers *http.Header, headerValues map[string]string) { + for key, value := range headerValues { + if len(value) > 0 { + headers.Add(key, value) + } + } +} + +// Request is in shared memory, so we have to make a shallow copy for further modification (imp is already a shallow copy) +func splitRequestDataByImp(request *openrtb2.BidRequest, imp openrtb2.Imp) openrtb2.BidRequest { + requestCopy := *request + requestCopy.Imp = []openrtb2.Imp{imp} + + return requestCopy +} + +func getYandexPlacementId(imp openrtb2.Imp) (*yandexPlacementID, error) { + var ext adapters.ExtImpBidder + if err := jsonutil.Unmarshal(imp.Ext, &ext); err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("imp %s: unable to unmarshal ext", imp.ID), + } + } + + var yandexExt openrtb_ext.ExtImpYandex + if err := jsonutil.Unmarshal(ext.Bidder, &yandexExt); err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("imp %s: unable to unmarshal ext.bidder: %v", imp.ID, err), + } + } + + placementID, err := mapExtToPlacementID(yandexExt) + if err != nil { + return nil, err + } + + return placementID, nil +} + +func mapExtToPlacementID(yandexExt openrtb_ext.ExtImpYandex) (*yandexPlacementID, error) { + var placementID yandexPlacementID + + if len(yandexExt.PlacementID) == 0 { + placementID.ImpID = strconv.Itoa(int(yandexExt.ImpID)) + placementID.PageID = strconv.Itoa(int(yandexExt.PageID)) + return &placementID, nil + } + + idParts := strings.Split(yandexExt.PlacementID, "-") + + numericIdParts := []string{} + + for _, idPart := range idParts { + if _, err := strconv.Atoi(idPart); err == nil { + numericIdParts = append(numericIdParts, idPart) + } + } + + if len(numericIdParts) < 2 { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("invalid placement id, it must contain two parts: %s", yandexExt.PlacementID), + } + } + + placementID.ImpID = numericIdParts[len(numericIdParts)-1] + placementID.PageID = numericIdParts[len(numericIdParts)-2] + + return &placementID, nil +} + +func modifyImp(imp *openrtb2.Imp) error { + if imp.Banner != nil { + banner, err := modifyBanner(*imp.Banner) + if banner != nil { + imp.Banner = banner + } + return err + } + + if imp.Native != nil { + return nil + } + + return &errortypes.BadInput{ + Message: fmt.Sprintf("Unsupported format. Yandex only supports banner and native types. Ignoring imp id #%s", imp.ID), + } +} + +func modifyBanner(banner openrtb2.Banner) (*openrtb2.Banner, error) { + format := banner.Format + + if banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0 { + if len(format) == 0 { + return nil, &errortypes.BadInput{ + Message: "Invalid size provided for Banner", + } + } + + firstFormat := format[0] + banner.H = &firstFormat.H + banner.W = &firstFormat.W + } + + return &banner, nil +} + +// "Un-templates" the endpoint by replacing macroses and adding the required query parameters +func (a *adapter) resolveUrl(placementID yandexPlacementID, referer string, currency string) (string, error) { + params := macros.EndpointTemplateParams{PageID: placementID.PageID} + + endpointStr, err := macros.ResolveMacros(a.endpoint, params) + if err != nil { + return "", err + } + + parsedUrl, err := url.Parse(endpointStr) + if err != nil { + return "", err + } + + addNonEmptyQueryParams(parsedUrl, map[string]string{ + refererQueryKey: referer, + currencyQueryKey: currency, + impIdQueryKey: placementID.ImpID, + }) + + return parsedUrl.String(), nil +} + +func addNonEmptyQueryParams(url *url.URL, queryMap map[string]string) { + query := url.Query() + for key, value := range queryMap { + if len(value) > 0 { + query.Add(key, value) + } + } + + url.RawQuery = query.Encode() +} + +func getReferer(request *openrtb2.BidRequest) string { + if request.Site == nil { + return "" + } + + return request.Site.Domain +} + +func getCurrency(request *openrtb2.BidRequest) string { + if len(request.Cur) == 0 { + return "" + } + + return request.Cur[0] +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + + if adapters.IsResponseStatusCodeNoContent(responseData) { + return nil, nil + } + + if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { + return nil, []error{err} + } + + var bidResponse openrtb2.BidResponse + if err := jsonutil.Unmarshal(responseData.Body, &bidResponse); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: %d", err), + }} + } + + bidResponseWithCapacity := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + + var errors []error + + impMap := map[string]*openrtb2.Imp{} + for i := range request.Imp { + imp := request.Imp[i] + + impMap[imp.ID] = &imp + } + + for _, seatBid := range bidResponse.SeatBid { + for i := range seatBid.Bid { + bid := seatBid.Bid[i] + + imp, exists := impMap[bid.ImpID] + if !exists { + errors = append(errors, &errortypes.BadInput{ + Message: fmt.Sprintf("Invalid bid imp ID #%s does not match any imp IDs from the original bid request", bid.ImpID), + }) + continue + } + + bidType, err := getBidType(*imp) + if err != nil { + errors = append(errors, err) + continue + } + + bidResponseWithCapacity.Bids = append(bidResponseWithCapacity.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + }) + } + } + + return bidResponseWithCapacity, errors +} + +func getBidType(imp openrtb2.Imp) (openrtb_ext.BidType, error) { + if imp.Native != nil { + return openrtb_ext.BidTypeNative, nil + } + + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } + + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Processing an invalid impression; cannot resolve impression type for imp #%s", imp.ID), + } +} diff --git a/adapters/yandex/yandex_test.go b/adapters/yandex/yandex_test.go new file mode 100644 index 00000000000..7635b1a1b93 --- /dev/null +++ b/adapters/yandex/yandex_test.go @@ -0,0 +1,29 @@ +package yandex + +import ( + "testing" + + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderYandex, + config.Adapter{Endpoint: "https://bs-metadsp.yandex.ru/prebid/{{.PageID}}?ssp-id=10500"}, + config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "yandextest", bidder) +} + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderYandex, config.Adapter{ + Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + assert.Error(t, buildErr) +} diff --git a/adapters/yandex/yandextest/exemplary/native.json b/adapters/yandex/yandextest/exemplary/native.json new file mode 100644 index 00000000000..880e4ca7b01 --- /dev/null +++ b/adapters/yandex/yandextest/exemplary/native.json @@ -0,0 +1,135 @@ +{ + "mockBidRequest": { + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "native": { + "request": "{}", + "ver": "1" + }, + "ext": { + "bidder": { + "placement_id": "R-134001-1" + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1", + "language": "EN" + }, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bs-metadsp.yandex.ru/prebid/134001?imp-id=1&ssp-cur=USD&ssp-id=10500&target-ref=www.example.com", + "headers": { + "User-Agent": [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" + ], + "X-Forwarded-For": [ + "127.0.0.1" + ], + "X-Real-Ip": [ + "127.0.0.1" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Referer": [ + "http://www.example.com" + ], + "Accept": [ + "application/json" + ], + "Accept-Language": [ + "EN" + ] + }, + "body": { + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "native": { + "request": "{}", + "ver": "1" + }, + "ext": { + "bidder": { + "placement_id": "R-134001-1" + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1", + "language": "EN" + }, + "tmax": 500 + }, + "impIDs":["imp_id"] + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "price": 1.25, + "crid": "crid", + "adm": "adm_content", + "h": 600, + "w": 300 + } + ] + } + ], + "bidid": "bid_id", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "bid_id", + "impid": "imp_id", + "price": 1.25, + "adm": "adm_content", + "crid": "crid", + "w": 300, + "h": 600 + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/yandex/yandextest/exemplary/simple-banner.json b/adapters/yandex/yandextest/exemplary/simple-banner.json new file mode 100644 index 00000000000..aaa30ae901c --- /dev/null +++ b/adapters/yandex/yandextest/exemplary/simple-banner.json @@ -0,0 +1,135 @@ +{ + "mockBidRequest": { + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 300, + "h": 600 + }, + "ext": { + "bidder": { + "placement_id": "134001-1" + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1", + "language": "EN" + }, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bs-metadsp.yandex.ru/prebid/134001?imp-id=1&ssp-cur=USD&ssp-id=10500&target-ref=www.example.com", + "headers": { + "User-Agent": [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" + ], + "X-Forwarded-For": [ + "127.0.0.1" + ], + "X-Real-Ip": [ + "127.0.0.1" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Referer": [ + "http://www.example.com" + ], + "Accept": [ + "application/json" + ], + "Accept-Language": [ + "EN" + ] + }, + "body": { + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 300, + "h": 600 + }, + "ext": { + "bidder": { + "placement_id": "134001-1" + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1", + "language": "EN" + }, + "tmax": 500 + }, + "impIDs":["imp_id"] + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "price": 1.25, + "crid": "crid", + "adm": "content", + "h": 600, + "w": 300 + } + ] + } + ], + "bidid": "bid_id", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "bid_id", + "impid": "imp_id", + "price": 1.25, + "adm": "content", + "crid": "crid", + "w": 300, + "h": 600 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/yandex/yandextest/supplemental/multiple-imps-some-malformed.json b/adapters/yandex/yandextest/supplemental/multiple-imps-some-malformed.json new file mode 100644 index 00000000000..daffeb3addd --- /dev/null +++ b/adapters/yandex/yandextest/supplemental/multiple-imps-some-malformed.json @@ -0,0 +1,146 @@ +{ + "mockBidRequest": { + "id": "request_id", + "imp": [ + { + "id": "imp_id1", + "banner": { + "w": 300, + "h": 600 + }, + "ext": { + "bidder": { + "page_id": 111, + "imp_id": 1 + } + } + }, + { + "id": "imp_id2", + "ext": { + "bidder": { + "page_id": 222, + "imp_id": 2 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1" + }, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bs-metadsp.yandex.ru/prebid/111?imp-id=1&ssp-cur=USD&ssp-id=10500&target-ref=www.example.com", + "headers": { + "User-Agent": [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" + ], + "X-Forwarded-For": [ + "127.0.0.1" + ], + "X-Real-Ip": [ + "127.0.0.1" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Referer": [ + "http://www.example.com" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "request_id", + "imp": [ + { + "id": "imp_id1", + "banner": { + "w": 300, + "h": 600 + }, + "ext": { + "bidder": { + "page_id": 111, + "imp_id": 1 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1" + }, + "tmax": 500 + }, + "impIDs":["imp_id1"] + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id1", + "impid": "imp_id1", + "price": 1.25, + "crid": "crid", + "adm": "adm001", + "h": 600, + "w": 300 + } + ] + } + ], + "bidid": "bid_id" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "bid_id1", + "impid": "imp_id1", + "price": 1.25, + "adm": "adm001", + "crid": "crid", + "w": 300, + "h": 600 + }, + "type": "banner" + } + ] + } + ], + "expectedMakeRequestsErrors": [ + { + "value": "Unsupported format", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/yandex/yandextest/supplemental/multiple-imps.json b/adapters/yandex/yandextest/supplemental/multiple-imps.json new file mode 100644 index 00000000000..34d105fd527 --- /dev/null +++ b/adapters/yandex/yandextest/supplemental/multiple-imps.json @@ -0,0 +1,237 @@ +{ + "mockBidRequest": { + "id": "request_id", + "imp": [ + { + "id": "imp_id1", + "banner": { + "w": 300, + "h": 600 + }, + "ext": { + "bidder": { + "page_id": 111, + "imp_id": 1 + } + } + }, + { + "id": "imp_id2", + "banner": { + "w": 400, + "h": 800 + }, + "ext": { + "bidder": { + "placement_id": "222-2" + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1" + }, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bs-metadsp.yandex.ru/prebid/111?imp-id=1&ssp-cur=USD&ssp-id=10500&target-ref=www.example.com", + "headers": { + "User-Agent": [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" + ], + "X-Forwarded-For": [ + "127.0.0.1" + ], + "X-Real-Ip": [ + "127.0.0.1" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Referer": [ + "http://www.example.com" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "request_id", + "imp": [ + { + "id": "imp_id1", + "banner": { + "w": 300, + "h": 600 + }, + "ext": { + "bidder": { + "page_id": 111, + "imp_id": 1 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1" + }, + "tmax": 500 + }, + "impIDs":["imp_id1"] + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id1", + "impid": "imp_id1", + "price": 1.25, + "crid": "crid", + "adm": "adm001", + "h": 600, + "w": 300 + } + ] + } + ], + "bidid": "bid_id" + } + } + }, + { + "expectedRequest": { + "uri": "https://bs-metadsp.yandex.ru/prebid/222?imp-id=2&ssp-cur=USD&ssp-id=10500&target-ref=www.example.com", + "headers": { + "User-Agent": [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" + ], + "X-Forwarded-For": [ + "127.0.0.1" + ], + "X-Real-Ip": [ + "127.0.0.1" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Referer": [ + "http://www.example.com" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "request_id", + "imp": [ + { + "id": "imp_id2", + "banner": { + "w": 400, + "h": 800 + }, + "ext": { + "bidder": { + "placement_id": "222-2" + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1" + }, + "tmax": 500 + }, + "impIDs":["imp_id2"] + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id2", + "impid": "imp_id2", + "price": 1.25, + "crid": "crid", + "adm": "adm001", + "h": 800, + "w": 400 + } + ] + } + ], + "bidid": "bid_id" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "bid_id1", + "impid": "imp_id1", + "price": 1.25, + "adm": "adm001", + "crid": "crid", + "w": 300, + "h": 600 + }, + "type": "banner" + } + ] + }, + { + "bids": [ + { + "bid": { + "id": "bid_id2", + "impid": "imp_id2", + "price": 1.25, + "adm": "adm001", + "crid": "crid", + "w": 400, + "h": 800 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/yandex/yandextest/supplemental/simple-banner-empty-response.json b/adapters/yandex/yandextest/supplemental/simple-banner-empty-response.json new file mode 100644 index 00000000000..77aec133069 --- /dev/null +++ b/adapters/yandex/yandextest/supplemental/simple-banner-empty-response.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 300, + "h": 600 + }, + "ext": { + "bidder": { + "page_id": 134001, + "imp_id": 1 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1" + }, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bs-metadsp.yandex.ru/prebid/134001?imp-id=1&ssp-cur=USD&ssp-id=10500&target-ref=www.example.com", + "headers": { + "User-Agent": [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" + ], + "X-Forwarded-For": [ + "127.0.0.1" + ], + "X-Real-Ip": [ + "127.0.0.1" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Referer": [ + "http://www.example.com" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 300, + "h": 600 + }, + "ext": { + "bidder": { + "page_id": 134001, + "imp_id": 1 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1" + }, + "tmax": 500 + }, + "impIDs":["imp_id"] + }, + "mockResponse": { + "status": 200, + "headers": {} + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Bad server response", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/yandex/yandextest/supplemental/simple-banner-empty-seatbid.json b/adapters/yandex/yandextest/supplemental/simple-banner-empty-seatbid.json new file mode 100644 index 00000000000..b3e5e352141 --- /dev/null +++ b/adapters/yandex/yandextest/supplemental/simple-banner-empty-seatbid.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 300, + "h": 600 + }, + "ext": { + "bidder": { + "page_id": 134001, + "imp_id": 1 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1" + }, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bs-metadsp.yandex.ru/prebid/134001?imp-id=1&ssp-cur=USD&ssp-id=10500&target-ref=www.example.com", + "headers": { + "User-Agent": [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" + ], + "X-Forwarded-For": [ + "127.0.0.1" + ], + "X-Real-Ip": [ + "127.0.0.1" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Referer": [ + "http://www.example.com" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 300, + "h": 600 + }, + "ext": { + "bidder": { + "page_id": 134001, + "imp_id": 1 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1" + }, + "tmax": 500 + }, + "impIDs":["imp_id"] + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "id": "request_id", + "seatbid": null, + "bidid": "bid_id", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [] + } + ], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/yandex/yandextest/supplemental/simple-banner-sizes.json b/adapters/yandex/yandextest/supplemental/simple-banner-sizes.json new file mode 100644 index 00000000000..66006513a4b --- /dev/null +++ b/adapters/yandex/yandextest/supplemental/simple-banner-sizes.json @@ -0,0 +1,233 @@ +{ + "mockBidRequest": { + "id": "request_id", + "imp": [ + { + "id": "invalid_width", + "banner": { + "w": 0, + "h": 600 + }, + "ext": { + "bidder": { + "page_id": 111, + "imp_id": 1 + } + } + }, + { + "id": "invalid_height", + "banner": { + "w": 400, + "h": 0 + }, + "ext": { + "bidder": { + "page_id": 222, + "imp_id": 2 + } + } + }, + { + "id": "invalid_size", + "banner": { + "w": 0, + "h": 0 + }, + "ext": { + "bidder": { + "page_id": 222, + "imp_id": 3 + } + } + }, + { + "id": "invalid_no_size_no_formats", + "banner": {}, + "ext": { + "bidder": { + "page_id": 222, + "imp_id": 5 + } + } + }, + { + "id": "invalid_size", + "banner": { + "w": 0, + "h": 0 + }, + "ext": { + "bidder": { + "page_id": 222, + "imp_id": 3 + } + } + }, + { + "id": "no_size_but_valid_formats", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "page_id": 222, + "imp_id": 4 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1" + }, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bs-metadsp.yandex.ru/prebid/222?imp-id=4&ssp-cur=USD&ssp-id=10500&target-ref=www.example.com", + "headers": { + "User-Agent": [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" + ], + "X-Forwarded-For": [ + "127.0.0.1" + ], + "X-Real-Ip": [ + "127.0.0.1" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Referer": [ + "http://www.example.com" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "request_id", + "imp": [ + { + "id": "no_size_but_valid_formats", + "banner": { + "w": 600, + "h": 500, + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "page_id": 222, + "imp_id": 4 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1" + }, + "tmax": 500 + }, + "impIDs":["no_size_but_valid_formats"] + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id4", + "impid": "no_size_but_valid_formats", + "price": 1.25, + "crid": "crid", + "adm": "adm001", + "h": 800, + "w": 400 + } + ] + } + ], + "bidid": "bid_id" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "bid_id4", + "impid": "no_size_but_valid_formats", + "price": 1.25, + "adm": "adm001", + "crid": "crid", + "w": 400, + "h": 800 + }, + "type": "banner" + } + ] + } + ], + "expectedMakeRequestsErrors": [ + { + "value": "Invalid size", + "comparison": "regex" + }, + { + "value": "Invalid size", + "comparison": "regex" + }, + { + "value": "Invalid size", + "comparison": "regex" + }, + { + "value": "Invalid size", + "comparison": "regex" + }, + { + "value": "Invalid size", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/yandex/yandextest/supplemental/simple-banner-status-400.json b/adapters/yandex/yandextest/supplemental/simple-banner-status-400.json new file mode 100644 index 00000000000..ed45e68653c --- /dev/null +++ b/adapters/yandex/yandextest/supplemental/simple-banner-status-400.json @@ -0,0 +1,102 @@ +{ + "mockBidRequest": { + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 300, + "h": 600 + }, + "ext": { + "bidder": { + "page_id": 134001, + "imp_id": 1 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1" + }, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bs-metadsp.yandex.ru/prebid/134001?imp-id=1&ssp-cur=USD&ssp-id=10500&target-ref=www.example.com", + "headers": { + "User-Agent": [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" + ], + "X-Forwarded-For": [ + "127.0.0.1" + ], + "X-Real-Ip": [ + "127.0.0.1" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Referer": [ + "http://www.example.com" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 300, + "h": 600 + }, + "ext": { + "bidder": { + "page_id": 134001, + "imp_id": 1 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1" + }, + "tmax": 500 + }, + "impIDs":["imp_id"] + }, + "mockResponse": { + "status": 400, + "headers": {}, + "body": {} + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/yandex/yandextest/supplemental/simple-banner-unknown-imp.json b/adapters/yandex/yandextest/supplemental/simple-banner-unknown-imp.json new file mode 100644 index 00000000000..991870a7025 --- /dev/null +++ b/adapters/yandex/yandextest/supplemental/simple-banner-unknown-imp.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 300, + "h": 600 + }, + "ext": { + "bidder": { + "page_id": 134001, + "imp_id": 1 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1" + }, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bs-metadsp.yandex.ru/prebid/134001?imp-id=1&ssp-cur=USD&ssp-id=10500&target-ref=www.example.com", + "headers": { + "User-Agent": [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" + ], + "X-Forwarded-For": [ + "127.0.0.1" + ], + "X-Real-Ip": [ + "127.0.0.1" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Referer": [ + "http://www.example.com" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 300, + "h": 600 + }, + "ext": { + "bidder": { + "page_id": 134001, + "imp_id": 1 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1" + }, + "tmax": 500 + }, + "impIDs":["imp_id"] + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id_unknown", + "price": 1.25, + "crid": "crid", + "adm": "content", + "h": 600, + "w": 300 + } + ] + } + ], + "bidid": "bid_id", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Invalid bid imp", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/yandex/yandextest/supplemental/simple-banner-unparsable-body.json b/adapters/yandex/yandextest/supplemental/simple-banner-unparsable-body.json new file mode 100644 index 00000000000..75c06a5f8de --- /dev/null +++ b/adapters/yandex/yandextest/supplemental/simple-banner-unparsable-body.json @@ -0,0 +1,102 @@ +{ + "mockBidRequest": { + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 300, + "h": 600 + }, + "ext": { + "bidder": { + "page_id": 134001, + "imp_id": 1 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1" + }, + "tmax": 500 + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bs-metadsp.yandex.ru/prebid/134001?imp-id=1&ssp-cur=USD&ssp-id=10500&target-ref=www.example.com", + "headers": { + "User-Agent": [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" + ], + "X-Forwarded-For": [ + "127.0.0.1" + ], + "X-Real-Ip": [ + "127.0.0.1" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Referer": [ + "http://www.example.com" + ], + "Accept": [ + "application/json" + ] + }, + "body": { + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 300, + "h": 600 + }, + "ext": { + "bidder": { + "page_id": 134001, + "imp_id": 1 + } + } + } + ], + "site": { + "domain": "www.example.com", + "page": "http://www.example.com" + }, + "cur": [ + "USD" + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1" + }, + "tmax": 500 + }, + "impIDs":["imp_id"] + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": "invalid" + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Bad server response", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/yandex/yandextest/supplemental/unknown-banner.json b/adapters/yandex/yandextest/supplemental/unknown-banner.json new file mode 100644 index 00000000000..a01b9037180 --- /dev/null +++ b/adapters/yandex/yandextest/supplemental/unknown-banner.json @@ -0,0 +1,32 @@ +{ + "mockBidRequest": { + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "video": { + "w": 100, + "h": 200 + }, + "ext": { + "bidder": { + "page_id": 134001, + "imp_id": 1 + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36", + "ip": "127.0.0.1" + }, + "tmax": 500 + }, + "httpCalls": [], + "expectedMakeRequestsErrors": [ + { + "value": "Unsupported format", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/yeahmobi/params_test.go b/adapters/yeahmobi/params_test.go index 997bf93a53f..2b47316df2d 100644 --- a/adapters/yeahmobi/params_test.go +++ b/adapters/yeahmobi/params_test.go @@ -2,8 +2,9 @@ package yeahmobi import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/yeahmobi/yeahmobi.go b/adapters/yeahmobi/yeahmobi.go index ede25387ce2..934cee5c27b 100644 --- a/adapters/yeahmobi/yeahmobi.go +++ b/adapters/yeahmobi/yeahmobi.go @@ -7,17 +7,24 @@ import ( "net/url" "text/template" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { EndpointTemplate *template.Template } +type yeahmobiBidExt struct { + VideoCreativeInfo *yeahmobiBidExtVideo `json:"video,omitempty"` +} +type yeahmobiBidExtVideo struct { + Duration *int `json:"duration,omitempty"` +} // Builder builds a new instance of the Yeahmobi adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { @@ -70,6 +77,7 @@ func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestDa Uri: endPoint, Body: reqBody, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }, errs } @@ -78,7 +86,7 @@ func transform(request *openrtb2.BidRequest) { if imp.Native != nil { var nativeRequest map[string]interface{} nativeCopyRequest := make(map[string]interface{}) - err := json.Unmarshal([]byte(request.Imp[i].Native.Request), &nativeRequest) + err := jsonutil.Unmarshal([]byte(request.Imp[i].Native.Request), &nativeRequest) //just ignore the bad native request if err == nil { _, exists := nativeRequest["native"] @@ -107,12 +115,12 @@ func getYeahmobiExt(request *openrtb2.BidRequest) (*openrtb_ext.ExtImpYeahmobi, for _, imp := range request.Imp { var extBidder adapters.ExtImpBidder - err := json.Unmarshal(imp.Ext, &extBidder) + err := jsonutil.Unmarshal(imp.Ext, &extBidder) if err != nil { errs = append(errs, err) continue } - err = json.Unmarshal(extBidder.Bidder, &extImpYeahmobi) + err = jsonutil.Unmarshal(extBidder.Bidder, &extImpYeahmobi) if err != nil { errs = append(errs, err) continue @@ -133,34 +141,42 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest if response.StatusCode == http.StatusNoContent { return nil, nil } - if response.StatusCode == http.StatusBadRequest { return nil, []error{&errortypes.BadInput{ Message: fmt.Sprintf("Unexpected status code: %d.", response.StatusCode), }} } - if response.StatusCode != http.StatusOK { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("Unexpected status code: %d.", response.StatusCode), }} } - var bidResp openrtb2.BidResponse - - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } - bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) - for _, sb := range bidResp.SeatBid { for i := range sb.Bid { var mediaType = getBidType(sb.Bid[i].ImpID, internalRequest.Imp) - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &sb.Bid[i], - BidType: mediaType, - }) + bid := sb.Bid[i] + typedBid := &adapters.TypedBid{ + Bid: &bid, + BidType: mediaType, + BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, + } + if bid.Ext != nil { + var bidExt *yeahmobiBidExt + err := jsonutil.Unmarshal(bid.Ext, &bidExt) + if err != nil { + return nil, []error{fmt.Errorf("bid.ext json unmarshal error")} + } else if bidExt != nil { + if bidExt.VideoCreativeInfo != nil && bidExt.VideoCreativeInfo.Duration != nil { + typedBid.BidVideo.Duration = *bidExt.VideoCreativeInfo.Duration + } + } + } + bidResponse.Bids = append(bidResponse.Bids, typedBid) } } return bidResponse, nil diff --git a/adapters/yeahmobi/yeahmobi_test.go b/adapters/yeahmobi/yeahmobi_test.go index 0b1c39ef214..d822b32d675 100644 --- a/adapters/yeahmobi/yeahmobi_test.go +++ b/adapters/yeahmobi/yeahmobi_test.go @@ -3,9 +3,9 @@ package yeahmobi import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/yeahmobi/yeahmobitest/exemplary/no-bid.json b/adapters/yeahmobi/yeahmobitest/exemplary/no-bid.json index 723cc40e664..86cc9ebe00d 100644 --- a/adapters/yeahmobi/yeahmobitest/exemplary/no-bid.json +++ b/adapters/yeahmobi/yeahmobitest/exemplary/no-bid.json @@ -37,7 +37,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/yeahmobi/yeahmobitest/exemplary/simple-banner.json b/adapters/yeahmobi/yeahmobitest/exemplary/simple-banner.json index 7499a7874e7..540214bb608 100644 --- a/adapters/yeahmobi/yeahmobitest/exemplary/simple-banner.json +++ b/adapters/yeahmobi/yeahmobitest/exemplary/simple-banner.json @@ -37,7 +37,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yeahmobi/yeahmobitest/exemplary/simple-native-1.1.json b/adapters/yeahmobi/yeahmobitest/exemplary/simple-native-1.1.json index 7e93eb68246..9224b7cb0dc 100644 --- a/adapters/yeahmobi/yeahmobitest/exemplary/simple-native-1.1.json +++ b/adapters/yeahmobi/yeahmobitest/exemplary/simple-native-1.1.json @@ -38,7 +38,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yeahmobi/yeahmobitest/exemplary/simple-native.json b/adapters/yeahmobi/yeahmobitest/exemplary/simple-native.json index 894e835bc07..406dbff268e 100644 --- a/adapters/yeahmobi/yeahmobitest/exemplary/simple-native.json +++ b/adapters/yeahmobi/yeahmobitest/exemplary/simple-native.json @@ -38,7 +38,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yeahmobi/yeahmobitest/exemplary/simple-video.json b/adapters/yeahmobi/yeahmobitest/exemplary/simple-video.json index b040d31b5f6..5538150f450 100644 --- a/adapters/yeahmobi/yeahmobitest/exemplary/simple-video.json +++ b/adapters/yeahmobi/yeahmobitest/exemplary/simple-video.json @@ -20,7 +20,6 @@ } ] }, - "httpCalls": [ { "expectedRequest": { @@ -29,7 +28,7 @@ "id": "test-request-id", "imp": [ { - "id":"test-imp-id", + "id": "test-imp-id", "video": { "w": 300, "h": 250, @@ -45,7 +44,10 @@ } } ] - } + }, + "impIDs": [ + "test-imp-id" + ] }, "mockResponse": { "status": 200, @@ -54,13 +56,20 @@ "seatbid": [ { "seat": "ttx", - "bid": [{ - "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id", - "price": 1.2, - "adm": "some-ads", - "crid": "crid_testid" - }] + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 1.2, + "adm": "some-ads", + "crid": "crid_testid", + "ext": { + "video": { + "duration": 300 + } + } + } + ] } ], "cur": "USD" @@ -68,7 +77,6 @@ } } ], - "expectedBidResponses": [ { "currency": "USD", @@ -79,9 +87,18 @@ "impid": "test-imp-id", "price": 1.2, "adm": "some-ads", - "crid": "crid_testid" + "crid": "crid_testid", + "ext": { + "video": { + "duration": 300 + } + } }, - "type": "video" + "type": "video", + "video": { + "duration": 300, + "primary_category": "" + } } ] } diff --git a/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext.json b/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext.json index 444e1e7a8d8..782f7a0c3b4 100644 --- a/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext.json +++ b/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext.json @@ -14,7 +14,7 @@ "expectedMakeRequestsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext_bidder.json b/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext_bidder.json index 89697d37141..da9dce00905 100644 --- a/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext_bidder.json +++ b/adapters/yeahmobi/yeahmobitest/supplemental/bad_imp_ext_bidder.json @@ -16,7 +16,7 @@ "expectedMakeRequestsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpYeahmobi", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json b/adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json index 0d77e5af93a..43e16117887 100644 --- a/adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json +++ b/adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json @@ -37,7 +37,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -49,7 +50,7 @@ "expectedMakeBidsErrors": [ { "comparison": "literal", - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse" + "value": "expect { or n, but found \"" } ] } diff --git a/adapters/yeahmobi/yeahmobitest/supplemental/status_400.json b/adapters/yeahmobi/yeahmobitest/supplemental/status_400.json index 74bb869218c..e337f6f21e8 100644 --- a/adapters/yeahmobi/yeahmobitest/supplemental/status_400.json +++ b/adapters/yeahmobi/yeahmobitest/supplemental/status_400.json @@ -37,7 +37,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/yeahmobi/yeahmobitest/supplemental/status_500.json b/adapters/yeahmobi/yeahmobitest/supplemental/status_500.json index 2d3264de897..434b11e1aad 100644 --- a/adapters/yeahmobi/yeahmobitest/supplemental/status_500.json +++ b/adapters/yeahmobi/yeahmobitest/supplemental/status_500.json @@ -37,7 +37,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 500, diff --git a/adapters/yieldlab/params_test.go b/adapters/yieldlab/params_test.go index ed0d2863629..ac65adc0fe7 100644 --- a/adapters/yieldlab/params_test.go +++ b/adapters/yieldlab/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // This file actually intends to test static/bidder-params/yieldlab.json diff --git a/adapters/yieldlab/types.go b/adapters/yieldlab/types.go index 90612700713..57ee15c923b 100644 --- a/adapters/yieldlab/types.go +++ b/adapters/yieldlab/types.go @@ -3,16 +3,59 @@ package yieldlab import ( "strconv" "time" + + "github.com/prebid/prebid-server/v3/openrtb_ext" ) type bidResponse struct { - ID uint64 `json:"id"` - Price uint `json:"price"` - Advertiser string `json:"advertiser"` - Adsize string `json:"adsize"` - Pid uint64 `json:"pid"` - Did uint64 `json:"did"` - Pvid string `json:"pvid"` + ID uint64 `json:"id"` + Price uint `json:"price"` + Advertiser string `json:"advertiser"` + Adsize string `json:"adsize"` + Pid uint64 `json:"pid"` + Did uint64 `json:"did"` + Pvid string `json:"pvid"` + DSA *dsaResponse `json:"dsa,omitempty"` +} + +// dsaResponse defines Digital Service Act (DSA) parameters from Yieldlab yieldprobe response. +type dsaResponse struct { + Behalf string `json:"behalf,omitempty"` + Paid string `json:"paid,omitempty"` + Adrender *int `json:"adrender,omitempty"` + Transparency []dsaTransparency `json:"transparency,omitempty"` +} + +// openRTBExtRegsWithDSA defines the contract for bidrequest.regs.ext with the missing DSA property. +// +// The openrtb_ext.ExtRegs needs to be extended on yieldlab adapter level until DSA has been implemented +// by the prebid server team (https://github.com/prebid/prebid-server/issues/3424). +type openRTBExtRegsWithDSA struct { + openrtb_ext.ExtRegs + DSA *dsaRequest `json:"dsa,omitempty"` +} + +// responseExtWithDSA defines seatbid.bid.ext with the DSA object. +type responseExtWithDSA struct { + DSA dsaResponse `json:"dsa"` +} + +// dsaRequest defines Digital Service Act (DSA) parameter +// as specified by the OpenRTB 2.X DSA Transparency community extension. +// +// Should rather come from openrtb_ext package but will be defined here until DSA has been +// implemented by the prebid server team (https://github.com/prebid/prebid-server/issues/3424). +type dsaRequest struct { + Required *int `json:"dsarequired"` + PubRender *int `json:"pubrender"` + DataToPub *int `json:"datatopub"` + Transparency []dsaTransparency `json:"transparency"` +} + +// dsaTransparency Digital Service Act (DSA) transparency object +type dsaTransparency struct { + Domain string `json:"domain,omitempty"` + Params []int `json:"dsaparams,omitempty"` } type cacheBuster func() string diff --git a/adapters/yieldlab/yieldlab.go b/adapters/yieldlab/yieldlab.go index 74ee6bd7220..7ffafc25d50 100644 --- a/adapters/yieldlab/yieldlab.go +++ b/adapters/yieldlab/yieldlab.go @@ -11,11 +11,13 @@ import ( "golang.org/x/text/currency" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" ) // YieldlabAdapter connects the Yieldlab API to prebid server @@ -66,8 +68,8 @@ func (a *YieldlabAdapter) makeEndpointURL(req *openrtb2.BidRequest, params *open } if req.Device.Geo != nil { - q.Set("lat", fmt.Sprintf("%v", req.Device.Geo.Lat)) - q.Set("lon", fmt.Sprintf("%v", req.Device.Geo.Lon)) + q.Set("lat", fmt.Sprintf("%v", ptrutil.ValueOrDefault(req.Device.Geo.Lat))) + q.Set("lon", fmt.Sprintf("%v", ptrutil.ValueOrDefault(req.Device.Geo.Lon))) } } @@ -95,11 +97,94 @@ func (a *YieldlabAdapter) makeEndpointURL(req *openrtb2.BidRequest, params *open } } + dsa, err := getDSA(req) + if err != nil { + return "", err + } + if dsa != nil { + if dsa.Required != nil { + q.Set("dsarequired", strconv.Itoa(*dsa.Required)) + } + if dsa.PubRender != nil { + q.Set("dsapubrender", strconv.Itoa(*dsa.PubRender)) + } + if dsa.DataToPub != nil { + q.Set("dsadatatopub", strconv.Itoa(*dsa.DataToPub)) + } + if len(dsa.Transparency) != 0 { + transparencyParam := makeDSATransparencyURLParam(dsa.Transparency) + if len(transparencyParam) != 0 { + q.Set("dsatransparency", transparencyParam) + } + } + } + uri.RawQuery = q.Encode() return uri.String(), nil } +// getDSA extracts the Digital Service Act (DSA) properties from the request. +func getDSA(req *openrtb2.BidRequest) (*dsaRequest, error) { + if req.Regs == nil || req.Regs.Ext == nil { + return nil, nil + } + + var extRegs openRTBExtRegsWithDSA + err := jsonutil.Unmarshal(req.Regs.Ext, &extRegs) + if err != nil { + return nil, fmt.Errorf("failed to parse Regs.Ext object from Yieldlab response: %v", err) + } + + return extRegs.DSA, nil +} + +// makeDSATransparencyURLParam creates the transparency url parameter +// as specified by the OpenRTB 2.X DSA Transparency community extension. +// +// Example result: platform1domain.com~1~~SSP2domain.com~1_2 +func makeDSATransparencyURLParam(transparencyObjects []dsaTransparency) string { + valueSeparator, itemSeparator, objectSeparator := "_", "~", "~~" + + var b strings.Builder + + concatParams := func(params []int) { + b.WriteString(strconv.Itoa(params[0])) + for _, param := range params[1:] { + b.WriteString(valueSeparator) + b.WriteString(strconv.Itoa(param)) + } + } + + concatTransparency := func(object dsaTransparency) { + if len(object.Domain) == 0 { + return + } + + b.WriteString(object.Domain) + if len(object.Params) != 0 { + b.WriteString(itemSeparator) + concatParams(object.Params) + } + } + + concatTransparencies := func(objects []dsaTransparency) { + if len(objects) == 0 { + return + } + + concatTransparency(objects[0]) + for _, obj := range objects[1:] { + b.WriteString(objectSeparator) + concatTransparency(obj) + } + } + + concatTransparencies(transparencyObjects) + + return b.String() +} + func (a *YieldlabAdapter) makeFormats(req *openrtb2.BidRequest) (bool, string) { var formats []string const sizesSeparator, adslotSizesSeparator = "|", "," @@ -123,7 +208,7 @@ func (a *YieldlabAdapter) getGDPR(request *openrtb2.BidRequest) (string, string, consent := "" if request.User != nil && request.User.Ext != nil { var extUser openrtb_ext.ExtUser - if err := json.Unmarshal(request.User.Ext, &extUser); err != nil { + if err := jsonutil.Unmarshal(request.User.Ext, &extUser); err != nil { return "", "", fmt.Errorf("failed to parse ExtUser in Yieldlab GDPR check: %v", err) } consent = extUser.Consent @@ -132,7 +217,7 @@ func (a *YieldlabAdapter) getGDPR(request *openrtb2.BidRequest) (string, string, gdpr := "" var extRegs openrtb_ext.ExtRegs if request.Regs != nil { - if err := json.Unmarshal(request.Regs.Ext, &extRegs); err == nil { + if err := jsonutil.Unmarshal(request.Regs.Ext, &extRegs); err == nil { if extRegs.GDPR != nil && (*extRegs.GDPR == 0 || *extRegs.GDPR == 1) { gdpr = strconv.Itoa(int(*extRegs.GDPR)) } @@ -177,6 +262,7 @@ func (a *YieldlabAdapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters Method: "GET", Uri: bidURL, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }}, nil } @@ -186,12 +272,12 @@ func (a *YieldlabAdapter) parseRequest(request *openrtb2.BidRequest) []*openrtb_ for i := 0; i < len(request.Imp); i++ { bidderExt := new(adapters.ExtImpBidder) - if err := json.Unmarshal(request.Imp[i].Ext, bidderExt); err != nil { + if err := jsonutil.Unmarshal(request.Imp[i].Ext, bidderExt); err != nil { continue } yieldlabExt := new(openrtb_ext.ExtImpYieldlab) - if err := json.Unmarshal(bidderExt.Bidder, yieldlabExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, yieldlabExt); err != nil { continue } @@ -229,7 +315,7 @@ func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa } bids := make([]*bidResponse, 0) - if err := json.Unmarshal(response.Body, &bids); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bids); err != nil { return nil, []error{ &errortypes.BadServerResponse{ Message: fmt.Sprintf("failed to parse bids response from yieldlab: %v", err), @@ -252,6 +338,7 @@ func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa } } + var bidErrors []error for _, bid := range bids { width, height, err := splitSize(bid.Adsize) if err != nil { @@ -268,7 +355,13 @@ func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa if imp, exists := adslotToImpMap[strconv.FormatUint(bid.ID, 10)]; !exists { continue } else { - var bidType openrtb_ext.BidType + extJson, err := makeResponseExt(bid) + if err != nil { + bidErrors = append(bidErrors, err) + // skip as bids with missing ext.dsa will be discarded anyway + continue + } + responseBid := &openrtb2.Bid{ ID: strconv.FormatUint(bid.ID, 10), Price: float64(bid.Price) / 100, @@ -277,8 +370,10 @@ func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa DealID: strconv.FormatUint(bid.Pid, 10), W: int64(width), H: int64(height), + Ext: extJson, } + var bidType openrtb_ext.BidType if imp.Video != nil { bidType = openrtb_ext.BidTypeVideo responseBid.NURL = a.makeAdSourceURL(internalRequest, req, bid) @@ -298,7 +393,18 @@ func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa } } - return bidderResponse, nil + return bidderResponse, bidErrors +} + +func makeResponseExt(bid *bidResponse) (json.RawMessage, error) { + if bid.DSA != nil { + extJson, err := json.Marshal(responseExtWithDSA{*bid.DSA}) + if err != nil { + return nil, fmt.Errorf("failed to make JSON for seatbid.bid.ext for adslotID %v. This is most likely a programming issue", bid.ID) + } + return extJson, nil + } + return nil, nil } func (a *YieldlabAdapter) findBidReq(adslotID uint64, params []*openrtb_ext.ExtImpYieldlab) *openrtb_ext.ExtImpYieldlab { @@ -315,9 +421,9 @@ func (a *YieldlabAdapter) findBidReq(adslotID uint64, params []*openrtb_ext.ExtI func (a *YieldlabAdapter) extractAdslotID(internalRequestImp openrtb2.Imp) string { bidderExt := new(adapters.ExtImpBidder) - json.Unmarshal(internalRequestImp.Ext, bidderExt) + jsonutil.Unmarshal(internalRequestImp.Ext, bidderExt) yieldlabExt := new(openrtb_ext.ExtImpYieldlab) - json.Unmarshal(bidderExt.Bidder, yieldlabExt) + jsonutil.Unmarshal(bidderExt.Bidder, yieldlabExt) return yieldlabExt.AdslotID } @@ -355,7 +461,7 @@ func (a *YieldlabAdapter) makeCreativeID(req *openrtb_ext.ExtImpYieldlab, bid *b // unmarshalSupplyChain makes the value for the schain URL parameter from the openRTB schain object. func unmarshalSupplyChain(req *openrtb2.BidRequest) *openrtb2.SupplyChain { var extSChain openrtb_ext.ExtRequestPrebidSChain - err := json.Unmarshal(req.Source.Ext, &extSChain) + err := jsonutil.Unmarshal(req.Source.Ext, &extSChain) if err != nil { // req.Source.Ext could be anything so don't handle any errors return nil @@ -393,20 +499,20 @@ func makeSupplyChain(openRtbSchain openrtb2.SupplyChain) string { // makeNodeValue converts any known value type from a schain node to a string and does URL encoding if necessary. func makeNodeValue(nodeParam any) string { - switch nodeParam.(type) { + switch nodeParam := nodeParam.(type) { case string: - return url.QueryEscape(nodeParam.(string)) + return url.QueryEscape(nodeParam) case *int8: - pointer := nodeParam.(*int8) + pointer := nodeParam if pointer == nil { return "" } return makeNodeValue(int(*pointer)) case int: - return strconv.Itoa(nodeParam.(int)) + return strconv.Itoa(nodeParam) case json.RawMessage: - if freeFormData := nodeParam.(json.RawMessage); freeFormData != nil { - freeFormJson, err := json.Marshal(freeFormData) + if nodeParam != nil { + freeFormJson, err := json.Marshal(nodeParam) if err != nil { return "" } diff --git a/adapters/yieldlab/yieldlab_test.go b/adapters/yieldlab/yieldlab_test.go index d3fc9f3eb1d..1bea9b12ea9 100644 --- a/adapters/yieldlab/yieldlab_test.go +++ b/adapters/yieldlab/yieldlab_test.go @@ -5,12 +5,12 @@ import ( "strconv" "testing" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) const testURL = "https://ad.yieldlab.net/testing/" @@ -257,6 +257,98 @@ func Test_makeSupplyChain(t *testing.T) { } } +func Test_makeDSATransparencyUrlParam(t *testing.T) { + tests := []struct { + name string + transparencies []dsaTransparency + expected string + }{ + { + name: "No transparency objects", + transparencies: []dsaTransparency{}, + expected: "", + }, + { + name: "Nil transparency", + transparencies: nil, + expected: "", + }, + { + name: "Params without a domain", + transparencies: []dsaTransparency{ + { + Params: []int{1, 2}, + }, + }, + expected: "", + }, + { + name: "Params without a params", + transparencies: []dsaTransparency{ + { + Domain: "domain.com", + }, + }, + expected: "domain.com", + }, + { + name: "One object; No Params", + transparencies: []dsaTransparency{ + { + Domain: "domain.com", + Params: []int{}, + }, + }, + expected: "domain.com", + }, + { + name: "One object; One Param", + transparencies: []dsaTransparency{ + { + Domain: "domain.com", + Params: []int{1}, + }, + }, + expected: "domain.com~1", + }, + { + name: "Three domain objects", + transparencies: []dsaTransparency{ + { + Domain: "domain1.com", + Params: []int{1, 2}, + }, + { + Domain: "domain2.com", + Params: []int{3, 4}, + }, + { + Domain: "domain3.com", + Params: []int{5, 6}, + }, + }, + expected: "domain1.com~1_2~~domain2.com~3_4~~domain3.com~5_6", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := makeDSATransparencyURLParam(test.transparencies) + assert.Equal(t, test.expected, actual) + }) + } +} + +func Test_getDSA_invalidRequestExt(t *testing.T) { + req := &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"DSA":"wrongValueType"}`)}, + } + + dsa, err := getDSA(req) + + assert.NotNil(t, err) + assert.Nil(t, dsa) +} + func TestYieldlabAdapter_makeEndpointURL_invalidEndpoint(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderYieldlab, config.Adapter{ Endpoint: "test$:/something§"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) diff --git a/adapters/yieldlab/yieldlabtest/exemplary/banner.json b/adapters/yieldlab/yieldlabtest/exemplary/banner.json index fea8b3da6ee..96fdc1410a6 100644 --- a/adapters/yieldlab/yieldlabtest/exemplary/banner.json +++ b/adapters/yieldlab/yieldlabtest/exemplary/banner.json @@ -73,7 +73,8 @@ "169.254.13.37" ] }, - "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&sizes=12345%3A728x90%7C800x300&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" + "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&sizes=12345%3A728x90%7C800x300&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads", + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yieldlab/yieldlabtest/exemplary/dsa.json b/adapters/yieldlab/yieldlabtest/exemplary/dsa.json new file mode 100644 index 00000000000..283f2ff813b --- /dev/null +++ b/adapters/yieldlab/yieldlabtest/exemplary/dsa.json @@ -0,0 +1,170 @@ +{ + "mockBidRequest": { + "id": "test-request-with-DSA", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "adslotId": "12345", + "supplyId": "123456789", + "targeting": { + "key1": "value1", + "key2": "value2" + }, + "extId": "abc" + } + } + } + ], + "device": { + "ifa": "hello-ads", + "devicetype": 4, + "connectiontype": 6, + "geo": { + "lat": 51.499488, + "lon": -0.128953 + }, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36", + "ip": "169.254.13.37", + "h": 1098, + "w": 814 + }, + "regs": { + "ext": { + "gdpr": 1, + "dsa": { + "dsarequired": 3, + "pubrender": 0, + "datatopub": 2, + "transparency": [ + { + "domain": "platform1domain.com", + "dsaparams": [ + 1 + ] + }, + { + "domain": "SSP2domain.com", + "dsaparams": [ + 1, + 2 + ] + } + ] + } + } + }, + "site": { + "id": "fake-site-id", + "publisher": { + "id": "1" + }, + "page": "http://localhost:9090/gdpr.html" + }, + "user": { + "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c", + "ext": { + "consent": "BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": [ + "application/json" + ], + "Cookie": [ + "id=34a53e82-0dc3-4815-8b7e-b725ede0361c" + ], + "Referer": [ + "http://localhost:9090/gdpr.html" + ], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" + ], + "X-Forwarded-For": [ + "169.254.13.37" + ] + }, + "uri": "https://ad.yieldlab.net/testing/12345?consent=BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02&content=json&dsadatatopub=2&dsapubrender=0&dsarequired=3&dsatransparency=platform1domain.com~1~~SSP2domain.com~1_2&gdpr=1&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&sizes=12345%3A728x90&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads", + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": [ + { + "id": 12345, + "price": 201, + "advertiser": "yieldlab", + "adsize": "728x90", + "pid": 1234, + "did": 5678, + "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5", + "dsa": { + "required": 3, + "behalf": "on behalf of yieldlab", + "paid": "by yieldlab", + "transparency": [ + { + "domain": "yieldlab.de", + "dsaparams": [ + 1, + 2 + ] + } + ], + "adrender": 0 + } + } + ] + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "adm": "", + "crid": "12345123433", + "dealid": "1234", + "ext": { + "dsa": { + "adrender": 0, + "transparency": [ + { + "domain": "yieldlab.de", + "dsaparams": [ + 1, + 2 + ] + } + ], + "behalf": "on behalf of yieldlab", + "paid": "by yieldlab" + } + }, + "id": "12345", + "impid": "test-imp-id", + "price": 2.01, + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/yieldlab/yieldlabtest/exemplary/gdpr.json b/adapters/yieldlab/yieldlabtest/exemplary/gdpr.json index 14e708289aa..31f3ca39bab 100644 --- a/adapters/yieldlab/yieldlabtest/exemplary/gdpr.json +++ b/adapters/yieldlab/yieldlabtest/exemplary/gdpr.json @@ -77,7 +77,8 @@ "169.254.13.37" ] }, - "uri": "https://ad.yieldlab.net/testing/12345?consent=BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02&content=json&gdpr=1&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&sizes=12345%3A728x90&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" + "uri": "https://ad.yieldlab.net/testing/12345?consent=BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02&content=json&gdpr=1&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&sizes=12345%3A728x90&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads", + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yieldlab/yieldlabtest/exemplary/mixed_types.json b/adapters/yieldlab/yieldlabtest/exemplary/mixed_types.json index 00f2185262f..44fa57851c6 100644 --- a/adapters/yieldlab/yieldlabtest/exemplary/mixed_types.json +++ b/adapters/yieldlab/yieldlabtest/exemplary/mixed_types.json @@ -98,7 +98,8 @@ "169.254.13.37" ] }, - "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" + "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads", + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yieldlab/yieldlabtest/exemplary/multiple_impressions.json b/adapters/yieldlab/yieldlabtest/exemplary/multiple_impressions.json index ead4be0de5a..1518e888fab 100644 --- a/adapters/yieldlab/yieldlabtest/exemplary/multiple_impressions.json +++ b/adapters/yieldlab/yieldlabtest/exemplary/multiple_impressions.json @@ -91,7 +91,8 @@ "169.254.13.37" ] }, - "uri": "https://ad.yieldlab.net/testing/12345,67890?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&sizes=12345%3A728x90%2C67890%3A300x250&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" + "uri": "https://ad.yieldlab.net/testing/12345,67890?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&sizes=12345%3A728x90%2C67890%3A300x250&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads", + "impIDs":["test-imp-id","test-imp-id2"] }, "mockResponse": { "status": 200, diff --git a/adapters/yieldlab/yieldlabtest/exemplary/schain.json b/adapters/yieldlab/yieldlabtest/exemplary/schain.json index e3e7230d4d3..878a9fa4e17 100644 --- a/adapters/yieldlab/yieldlabtest/exemplary/schain.json +++ b/adapters/yieldlab/yieldlabtest/exemplary/schain.json @@ -93,7 +93,8 @@ "169.254.13.37" ] }, - "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&schain=1.0%2C1%21exchange1.com%2C1234%2521abcd%2C1%2Cbid%2Brequest%2526%25251%2Cpublisher%2Cpublisher.com%2C%257B%2522freeFormData%2522%253A1%252C%2522nested%2522%253A%257B%2522isTrue%2522%253Atrue%257D%257D&sizes=12345%3A728x90&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" + "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&schain=1.0%2C1%21exchange1.com%2C1234%2521abcd%2C1%2Cbid%2Brequest%2526%25251%2Cpublisher%2Cpublisher.com%2C%257B%2522freeFormData%2522%253A1%252C%2522nested%2522%253A%257B%2522isTrue%2522%253Atrue%257D%257D&sizes=12345%3A728x90&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads", + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yieldlab/yieldlabtest/exemplary/schain_multiple_nodes.json b/adapters/yieldlab/yieldlabtest/exemplary/schain_multiple_nodes.json index d976898f29f..af81b32a620 100644 --- a/adapters/yieldlab/yieldlabtest/exemplary/schain_multiple_nodes.json +++ b/adapters/yieldlab/yieldlabtest/exemplary/schain_multiple_nodes.json @@ -91,7 +91,8 @@ "169.254.13.37" ] }, - "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&schain=1.0%2C1%21exchange1.com%2C1234%2C1%2C%2C%2C%2C%2522text%2522%21exchange2.com%2Cabcd%2C1%2C%2C%2C%2C1&sizes=12345%3A728x90&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" + "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&schain=1.0%2C1%21exchange1.com%2C1234%2C1%2C%2C%2C%2C%2522text%2522%21exchange2.com%2Cabcd%2C1%2C%2C%2C%2C1&sizes=12345%3A728x90&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads", + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yieldlab/yieldlabtest/exemplary/video.json b/adapters/yieldlab/yieldlabtest/exemplary/video.json index 3d545a419bb..b9d10123f78 100644 --- a/adapters/yieldlab/yieldlabtest/exemplary/video.json +++ b/adapters/yieldlab/yieldlabtest/exemplary/video.json @@ -86,7 +86,8 @@ "169.254.13.37" ] }, - "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" + "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads", + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yieldlab/yieldlabtest/exemplary/video_app.json b/adapters/yieldlab/yieldlabtest/exemplary/video_app.json index d9980e221a3..8dbc5f97f64 100644 --- a/adapters/yieldlab/yieldlabtest/exemplary/video_app.json +++ b/adapters/yieldlab/yieldlabtest/exemplary/video_app.json @@ -86,7 +86,8 @@ "169.254.13.37" ] }, - "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pubappname=Awesome+App&pubbundlename=com.app.awesome&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" + "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pubappname=Awesome+App&pubbundlename=com.app.awesome&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads", + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yieldlab/yieldlabtest/supplemental/dsa_empty.json b/adapters/yieldlab/yieldlabtest/supplemental/dsa_empty.json new file mode 100644 index 00000000000..8be11178121 --- /dev/null +++ b/adapters/yieldlab/yieldlabtest/supplemental/dsa_empty.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "test-request-with-empty-DSA", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "adslotId": "12345", + "supplyId": "123456789", + "targeting": { + "key1": "value1", + "key2": "value2" + }, + "extId": "abc" + } + } + } + ], + "device": { + "ifa": "hello-ads", + "devicetype": 4, + "connectiontype": 6, + "geo": { + "lat": 51.499488, + "lon": -0.128953 + }, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36", + "ip": "169.254.13.37", + "h": 1098, + "w": 814 + }, + "regs": { + "ext": { + "gdpr": 1, + "dsa": { + } + } + }, + "site": { + "id": "fake-site-id", + "publisher": { + "id": "1" + }, + "page": "http://localhost:9090/gdpr.html" + }, + "user": { + "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c", + "ext": { + "consent": "BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": [ + "application/json" + ], + "Cookie": [ + "id=34a53e82-0dc3-4815-8b7e-b725ede0361c" + ], + "Referer": [ + "http://localhost:9090/gdpr.html" + ], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" + ], + "X-Forwarded-For": [ + "169.254.13.37" + ] + }, + "uri": "https://ad.yieldlab.net/testing/12345?consent=BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02&content=json&gdpr=1&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&sizes=12345%3A728x90&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads", + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": [ + { + "id": 12345, + "price": 201, + "advertiser": "yieldlab", + "adsize": "728x90", + "pid": 1234, + "did": 5678, + "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5", + "dsa": { + } + } + ] + + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "adm": "", + "crid": "12345123433", + "dealid": "1234", + "ext": { + "dsa": { + } + }, + "id": "12345", + "impid": "test-imp-id", + "price": 2.01, + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/yieldlab/yieldlabtest/supplemental/dsa_empty_transparency.json b/adapters/yieldlab/yieldlabtest/supplemental/dsa_empty_transparency.json new file mode 100644 index 00000000000..a240105fb90 --- /dev/null +++ b/adapters/yieldlab/yieldlabtest/supplemental/dsa_empty_transparency.json @@ -0,0 +1,140 @@ +{ + "mockBidRequest": { + "id": "test-request-with-empty-DSA-transparency", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "adslotId": "12345", + "supplyId": "123456789", + "targeting": { + "key1": "value1", + "key2": "value2" + }, + "extId": "abc" + } + } + } + ], + "device": { + "ifa": "hello-ads", + "devicetype": 4, + "connectiontype": 6, + "geo": { + "lat": 51.499488, + "lon": -0.128953 + }, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36", + "ip": "169.254.13.37", + "h": 1098, + "w": 814 + }, + "regs": { + "ext": { + "gdpr": 1, + "dsa": { + "transparency": [ + { + } + ] + } + } + }, + "site": { + "id": "fake-site-id", + "publisher": { + "id": "1" + }, + "page": "http://localhost:9090/gdpr.html" + }, + "user": { + "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c", + "ext": { + "consent": "BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": [ + "application/json" + ], + "Cookie": [ + "id=34a53e82-0dc3-4815-8b7e-b725ede0361c" + ], + "Referer": [ + "http://localhost:9090/gdpr.html" + ], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" + ], + "X-Forwarded-For": [ + "169.254.13.37" + ] + }, + "uri": "https://ad.yieldlab.net/testing/12345?consent=BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02&content=json&gdpr=1&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&sizes=12345%3A728x90&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads", + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": [ + { + "id": 12345, + "price": 201, + "advertiser": "yieldlab", + "adsize": "728x90", + "pid": 1234, + "did": 5678, + "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5", + "dsa": { + "transparency": [ + { + } + ] + } + } + ] + + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "adm": "", + "crid": "12345123433", + "dealid": "1234", + "ext": { + "dsa": { + "transparency": [ + { + } + ] + } + }, + "id": "12345", + "impid": "test-imp-id", + "price": 2.01, + "w": 728, + "h": 90 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/yieldlab/yieldlabtest/supplemental/invalid_reg_ext.json b/adapters/yieldlab/yieldlabtest/supplemental/invalid_reg_ext.json new file mode 100644 index 00000000000..51e8e87d540 --- /dev/null +++ b/adapters/yieldlab/yieldlabtest/supplemental/invalid_reg_ext.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "id": "test-request-with-wrong-DSA-type", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "adslotId": "12345", + "supplyId": "123456789", + "targeting": { + "key1": "value1", + "key2": "value2" + }, + "extId": "abc" + } + } + } + ], + "device": { + "ifa": "hello-ads", + "devicetype": 4, + "connectiontype": 6, + "geo": { + "lat": 51.499488, + "lon": -0.128953 + }, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36", + "ip": "169.254.13.37", + "h": 1098, + "w": 814 + }, + "regs": { + "ext": { + "gdpr": 1, + "DSA": "" + } + }, + "site": { + "id": "fake-site-id", + "publisher": { + "id": "1" + }, + "page": "http://localhost:9090/gdpr.html" + }, + "user": { + "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c", + "ext": { + "consent": "BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02" + } + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "failed to parse Regs.Ext object from Yieldlab response: cannot unmarshal yieldlab.openRTBExtRegsWithDSA.DSA: expect { or n, but found \"", + "comparison": "literal" + } + ] +} diff --git a/adapters/yieldmo/params_test.go b/adapters/yieldmo/params_test.go index d94c7ff035b..baa566cf713 100644 --- a/adapters/yieldmo/params_test.go +++ b/adapters/yieldmo/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // This file actually intends to test static/bidder-params/yieldmo.json diff --git a/adapters/yieldmo/yieldmo.go b/adapters/yieldmo/yieldmo.go index bf8410b294b..0725f7920c3 100644 --- a/adapters/yieldmo/yieldmo.go +++ b/adapters/yieldmo/yieldmo.go @@ -4,12 +4,14 @@ import ( "encoding/json" "fmt" "net/http" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "strings" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type YieldmoAdapter struct { @@ -30,25 +32,16 @@ type Ext struct { Gpid string `json:"gpid,omitempty"` } -func (a *YieldmoAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - var errs []error - var adapterRequests []*adapters.RequestData - - adapterReq, errors := a.makeRequest(request) - if adapterReq != nil { - adapterRequests = append(adapterRequests, adapterReq) - } - errs = append(errs, errors...) - - return adapterRequests, errors +type ExtBid struct { + MediaType string `json:"mediatype,omitempty"` } -func (a *YieldmoAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { +func (a *YieldmoAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error - if err := preprocess(request); err != nil { - errs = append(errs, err) - } + preprocessErrors := preprocess(request, reqInfo) + + errs = append(errs, preprocessErrors...) // Last Step reqJSON, err := json.Marshal(request) @@ -60,32 +53,47 @@ func (a *YieldmoAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.Re headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") - return &adapters.RequestData{ + return []*adapters.RequestData{{ Method: "POST", Uri: a.endpoint, Body: reqJSON, Headers: headers, - }, errs + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), + }}, errs } // Mutate the request to get it ready to send to yieldmo. -func preprocess(request *openrtb2.BidRequest) error { +func preprocess(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) []error { + var errs []error + for i := 0; i < len(request.Imp); i++ { var imp = request.Imp[i] var bidderExt ExtImpBidderYieldmo - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - return &errortypes.BadInput{ - Message: err.Error(), + if imp.BidFloor > 0 && imp.BidFloorCur != "" && strings.ToUpper(imp.BidFloorCur) != "USD" { + floor, err := reqInfo.ConvertCurrency(imp.BidFloor, imp.BidFloorCur, "USD") + if err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: fmt.Sprintf("Unable to convert provided bid floor currency from %s to USD", imp.BidFloorCur), + }) + } else { + request.Imp[i].BidFloorCur = "USD" + request.Imp[i].BidFloor = floor } } + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: err.Error(), + }) + } + var yieldmoExt openrtb_ext.ExtImpYieldmo - if err := json.Unmarshal(bidderExt.Bidder, &yieldmoExt); err != nil { - return &errortypes.BadInput{ + if err := jsonutil.Unmarshal(bidderExt.Bidder, &yieldmoExt); err != nil { + errs = append(errs, &errortypes.BadInput{ Message: err.Error(), - } + }) } var impExt Ext @@ -99,15 +107,15 @@ func preprocess(request *openrtb2.BidRequest) error { impExtJSON, err := json.Marshal(impExt) if err != nil { - return &errortypes.BadInput{ + errs = append(errs, &errortypes.BadInput{ Message: err.Error(), - } + }) } request.Imp[i].Ext = impExtJSON } - return nil + return errs } // MakeBids make the bids for the bid response. @@ -130,7 +138,7 @@ func (a *YieldmoAdapter) MakeBids(internalRequest *openrtb2.BidRequest, external var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -138,14 +146,18 @@ func (a *YieldmoAdapter) MakeBids(internalRequest *openrtb2.BidRequest, external for _, sb := range bidResp.SeatBid { for i := range sb.Bid { + bidType, err := getMediaTypeForImp(sb.Bid[i]) + if err != nil { + continue + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &sb.Bid[i], - BidType: getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp), + BidType: bidType, }) } } return bidResponse, nil - } // Builder builds a new instance of the Yieldmo adapter for the given bidder with the given config. @@ -156,12 +168,21 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co return bidder, nil } -func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { - //default to video unless banner exists in impression - for _, imp := range imps { - if imp.ID == impId && imp.Banner != nil { - return openrtb_ext.BidTypeBanner - } +// Retrieve the media type corresponding to the bid from the bid.ext object +func getMediaTypeForImp(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + var bidExt ExtBid + if err := jsonutil.Unmarshal(bid.Ext, &bidExt); err != nil { + return "", &errortypes.BadInput{Message: err.Error()} + } + + switch bidExt.MediaType { + case "banner": + return openrtb_ext.BidTypeBanner, nil + case "video": + return openrtb_ext.BidTypeVideo, nil + case "native": + return openrtb_ext.BidTypeNative, nil + default: + return "", fmt.Errorf("invalid BidType: %s", bidExt.MediaType) } - return openrtb_ext.BidTypeVideo } diff --git a/adapters/yieldmo/yieldmo_test.go b/adapters/yieldmo/yieldmo_test.go index 1d9426d0643..668e0a375ea 100644 --- a/adapters/yieldmo/yieldmo_test.go +++ b/adapters/yieldmo/yieldmo_test.go @@ -3,9 +3,9 @@ package yieldmo import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/yieldmo/yieldmotest/exemplary/app-banner.json b/adapters/yieldmo/yieldmotest/exemplary/app-banner.json index e674cf11539..7ff5f0de8d1 100644 --- a/adapters/yieldmo/yieldmotest/exemplary/app-banner.json +++ b/adapters/yieldmo/yieldmotest/exemplary/app-banner.json @@ -48,7 +48,8 @@ "app": { "id": "fake-app-id" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -65,7 +66,11 @@ "adm": "some-test-ad", "crid": "crid_10", "h": 250, - "w": 300 + "w": 300, + "ext": + { + "mediatype": "banner" + } } ] } @@ -87,7 +92,11 @@ "adm": "some-test-ad", "crid": "crid_10", "w": 300, - "h": 250 + "h": 250, + "ext": + { + "mediatype": "banner" + } }, "type": "banner" } diff --git a/adapters/yieldmo/yieldmotest/exemplary/app_video.json b/adapters/yieldmo/yieldmotest/exemplary/app_video.json index e33c37f69bf..040906e7719 100644 --- a/adapters/yieldmo/yieldmotest/exemplary/app_video.json +++ b/adapters/yieldmo/yieldmotest/exemplary/app_video.json @@ -46,7 +46,8 @@ "app": { "id": "fake-app-id" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -63,7 +64,11 @@ "adm": "some-test-ad", "crid": "crid_10", "h": 250, - "w": 300 + "w": 300, + "ext": + { + "mediatype": "video" + } } ] } @@ -85,7 +90,11 @@ "adm": "some-test-ad", "crid": "crid_10", "w": 300, - "h": 250 + "h": 250, + "ext": + { + "mediatype": "video" + } }, "type": "video" } diff --git a/adapters/yieldmo/yieldmotest/exemplary/simple-banner.json b/adapters/yieldmo/yieldmotest/exemplary/simple-banner.json index 11739ca1d32..9a5028db04a 100644 --- a/adapters/yieldmo/yieldmotest/exemplary/simple-banner.json +++ b/adapters/yieldmo/yieldmotest/exemplary/simple-banner.json @@ -21,6 +21,15 @@ ], "site": { "id": "fake-site-id" + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString", + "gpp": "gppString", + "gpp_sid": [6] + } } }, "httpCalls": [ @@ -47,8 +56,18 @@ ], "site": { "id": "fake-site-id" + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString", + "gpp": "gppString", + "gpp_sid": [6] + } } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -65,7 +84,11 @@ "adm": "some-test-ad", "crid": "crid_10", "h": 250, - "w": 300 + "w": 300, + "ext": + { + "mediatype": "banner" + } } ] } @@ -87,7 +110,11 @@ "adm": "some-test-ad", "crid": "crid_10", "w": 300, - "h": 250 + "h": 250, + "ext": + { + "mediatype": "banner" + } }, "type": "banner" } diff --git a/adapters/yieldmo/yieldmotest/exemplary/simple_video.json b/adapters/yieldmo/yieldmotest/exemplary/simple_video.json index aaf53124365..010b35a1471 100644 --- a/adapters/yieldmo/yieldmotest/exemplary/simple_video.json +++ b/adapters/yieldmo/yieldmotest/exemplary/simple_video.json @@ -46,7 +46,8 @@ "site": { "id": "fake-site-id" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -63,7 +64,11 @@ "adm": "some-test-ad", "crid": "crid_10", "h": 250, - "w": 300 + "w": 300, + "ext": + { + "mediatype": "video" + } } ] } @@ -85,7 +90,11 @@ "adm": "some-test-ad", "crid": "crid_10", "w": 300, - "h": 250 + "h": 250, + "ext": + { + "mediatype": "video" + } }, "type": "video" } diff --git a/adapters/yieldmo/yieldmotest/exemplary/valid_currency_conversion.json b/adapters/yieldmo/yieldmotest/exemplary/valid_currency_conversion.json new file mode 100644 index 00000000000..15b4aa3a25c --- /dev/null +++ b/adapters/yieldmo/yieldmotest/exemplary/valid_currency_conversion.json @@ -0,0 +1,150 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "bidfloor": 1.0, + "bidfloorcur": "EUR", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "123" + } + } + } + ], + "site": { + "id": "fake-site-id" + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString", + "gpp": "gppString", + "gpp_sid": [6] + } + }, + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 1.1 + } + } + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ads.yieldmo.com/openrtb2", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "bidfloor": 1.1, + "bidfloorcur": "USD", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "placement_id": "123" + } + } + ], + "site": { + "id": "fake-site-id" + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString", + "gpp": "gppString", + "gpp_sid": [6] + } + }, + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "USD": 1.1 + } + } + } + } + } + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "yieldmo", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 1.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300, + "ext": + { + "mediatype": "banner" + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 1.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 300, + "h": 250, + "ext": + { + "mediatype": "banner" + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/yieldmo/yieldmotest/exemplary/with_gpid.json b/adapters/yieldmo/yieldmotest/exemplary/with_gpid.json index bd9e911058a..12785c7fdb8 100644 --- a/adapters/yieldmo/yieldmotest/exemplary/with_gpid.json +++ b/adapters/yieldmo/yieldmotest/exemplary/with_gpid.json @@ -56,7 +56,8 @@ "site": { "id": "fake-site-id" } - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -73,7 +74,11 @@ "adm": "some-test-ad", "crid": "crid_10", "h": 250, - "w": 300 + "w": 300, + "ext": + { + "mediatype": "banner" + } } ] } @@ -95,7 +100,11 @@ "adm": "some-test-ad", "crid": "crid_10", "w": 300, - "h": 250 + "h": 250, + "ext": + { + "mediatype": "banner" + } }, "type": "banner" } diff --git a/adapters/yieldmo/yieldmotest/supplemental/unsupported_currency.json b/adapters/yieldmo/yieldmotest/supplemental/unsupported_currency.json new file mode 100644 index 00000000000..a6d108aa38a --- /dev/null +++ b/adapters/yieldmo/yieldmotest/supplemental/unsupported_currency.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "bidfloor": 1.0, + "bidfloorcur": "EUR", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "123" + } + } + } + ], + "site": { + "id": "fake-site-id" + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString", + "gpp": "gppString", + "gpp_sid": [6] + } + }, + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "GBP": 0.85 + } + } + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ads.yieldmo.com/openrtb2", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "bidfloor": 1.0, + "bidfloorcur": "EUR", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "placement_id": "123" + } + } + ], + "site": { + "id": "fake-site-id" + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString", + "gpp": "gppString", + "gpp_sid": [6] + } + }, + "ext": { + "prebid": { + "currency": { + "rates": { + "EUR": { + "GBP": 0.85 + } + } + } + } + } + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedMakeRequestsErrors": [ + { + "value": "Unable to convert provided bid floor currency from EUR to USD", + "comparison": "literal" + } + ] +} diff --git a/adapters/yieldone/params_test.go b/adapters/yieldone/params_test.go index 6048ea5d7dc..5632546ac31 100644 --- a/adapters/yieldone/params_test.go +++ b/adapters/yieldone/params_test.go @@ -2,8 +2,9 @@ package yieldone import ( "encoding/json" - "github.com/prebid/prebid-server/openrtb_ext" "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/yieldone/yieldone.go b/adapters/yieldone/yieldone.go index 2d5f1d81173..b1aa6465386 100644 --- a/adapters/yieldone/yieldone.go +++ b/adapters/yieldone/yieldone.go @@ -5,11 +5,12 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type YieldoneAdapter struct { @@ -45,6 +46,7 @@ func (a *YieldoneAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad Uri: a.endpoint, Body: reqJSON, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), }}, errors } @@ -67,7 +69,7 @@ func (a *YieldoneAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa } var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -104,11 +106,11 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co func preprocess(imp *openrtb2.Imp) error { var ext adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &ext); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &ext); err != nil { return err } var impressionExt openrtb_ext.ExtImpYieldone - if err := json.Unmarshal(ext.Bidder, &impressionExt); err != nil { + if err := jsonutil.Unmarshal(ext.Bidder, &impressionExt); err != nil { return err } diff --git a/adapters/yieldone/yieldone_test.go b/adapters/yieldone/yieldone_test.go index 12d634d463d..a9831561d43 100644 --- a/adapters/yieldone/yieldone_test.go +++ b/adapters/yieldone/yieldone_test.go @@ -3,9 +3,9 @@ package yieldone import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/yieldone/yieldonetest/exemplary/simple-banner.json b/adapters/yieldone/yieldonetest/exemplary/simple-banner.json index f84476f1e86..c5d3566ba2f 100644 --- a/adapters/yieldone/yieldonetest/exemplary/simple-banner.json +++ b/adapters/yieldone/yieldonetest/exemplary/simple-banner.json @@ -44,7 +44,8 @@ } } }] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yieldone/yieldonetest/exemplary/simple-video.json b/adapters/yieldone/yieldonetest/exemplary/simple-video.json index dc313abede7..d6607df739a 100644 --- a/adapters/yieldone/yieldonetest/exemplary/simple-video.json +++ b/adapters/yieldone/yieldonetest/exemplary/simple-video.json @@ -42,7 +42,8 @@ } } }] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/yieldone/yieldonetest/supplemental/bad_response.json b/adapters/yieldone/yieldonetest/supplemental/bad_response.json index 3112d1f7ba0..c3ce0ed0c65 100644 --- a/adapters/yieldone/yieldonetest/supplemental/bad_response.json +++ b/adapters/yieldone/yieldonetest/supplemental/bad_response.json @@ -47,7 +47,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 200, @@ -58,7 +59,7 @@ "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/yieldone/yieldonetest/supplemental/status_204.json b/adapters/yieldone/yieldonetest/supplemental/status_204.json index b1c9304a35a..f83cd058f26 100644 --- a/adapters/yieldone/yieldonetest/supplemental/status_204.json +++ b/adapters/yieldone/yieldonetest/supplemental/status_204.json @@ -47,7 +47,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 204, diff --git a/adapters/yieldone/yieldonetest/supplemental/status_400.json b/adapters/yieldone/yieldonetest/supplemental/status_400.json index 1cb172bb371..b4c60f5b8a0 100644 --- a/adapters/yieldone/yieldonetest/supplemental/status_400.json +++ b/adapters/yieldone/yieldonetest/supplemental/status_400.json @@ -47,7 +47,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 400, diff --git a/adapters/yieldone/yieldonetest/supplemental/status_418.json b/adapters/yieldone/yieldonetest/supplemental/status_418.json index 30cc16adde5..480b9d65dbc 100644 --- a/adapters/yieldone/yieldonetest/supplemental/status_418.json +++ b/adapters/yieldone/yieldonetest/supplemental/status_418.json @@ -47,7 +47,8 @@ } } ] - } + }, + "impIDs":["test-imp-id"] }, "mockResponse": { "status": 418, diff --git a/adapters/zeroclickfraud/zeroclickfraud.go b/adapters/zeroclickfraud/zeroclickfraud.go index 235f678d7bb..eecd1dd8589 100644 --- a/adapters/zeroclickfraud/zeroclickfraud.go +++ b/adapters/zeroclickfraud/zeroclickfraud.go @@ -7,12 +7,13 @@ import ( "strconv" "text/template" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type ZeroClickFraudAdapter struct { @@ -57,7 +58,9 @@ func (a *ZeroClickFraudAdapter) MakeRequests(request *openrtb2.BidRequest, reqIn Method: "POST", Uri: url, Body: reqJson, - Headers: headers} + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), + } requests = append(requests, &request) } @@ -90,7 +93,7 @@ func (a *ZeroClickFraudAdapter) MakeBids( var bidResp openrtb2.BidResponse - if err := json.Unmarshal(response.Body, &bidResp); err != nil { + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -128,13 +131,13 @@ func splitImpressions(imps []openrtb2.Imp) (map[openrtb_ext.ExtImpZeroClickFraud func getBidderParams(imp *openrtb2.Imp) (*openrtb_ext.ExtImpZeroClickFraud, error) { var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ Message: fmt.Sprintf("Missing bidder ext: %s", err.Error()), } } var zeroclickfraudExt openrtb_ext.ExtImpZeroClickFraud - if err := json.Unmarshal(bidderExt.Bidder, &zeroclickfraudExt); err != nil { + if err := jsonutil.Unmarshal(bidderExt.Bidder, &zeroclickfraudExt); err != nil { return nil, &errortypes.BadInput{ Message: fmt.Sprintf("Cannot Resolve host or sourceId: %s", err.Error()), } diff --git a/adapters/zeroclickfraud/zeroclickfraud_test.go b/adapters/zeroclickfraud/zeroclickfraud_test.go index e07c43ff7a2..363f37c0e17 100644 --- a/adapters/zeroclickfraud/zeroclickfraud_test.go +++ b/adapters/zeroclickfraud/zeroclickfraud_test.go @@ -3,9 +3,9 @@ package zeroclickfraud import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/multi-request.json b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/multi-request.json index 70bfb9645c8..d8c99121e94 100644 --- a/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/multi-request.json +++ b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/multi-request.json @@ -106,7 +106,8 @@ }, "at": 1, "tmax": 500 - } + }, + "impIDs":["some-impression-id","some-impression-id2"] }, "mockResponse": { diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/native.json b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/native.json index dcf9064f29d..2494cd2582e 100644 --- a/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/native.json +++ b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/native.json @@ -73,7 +73,8 @@ }, "at": 1, "tmax": 500 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-banner.json b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-banner.json index 1d5ee3b3a52..a35cf8ec38f 100644 --- a/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-banner.json +++ b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-banner.json @@ -79,7 +79,8 @@ }, "at": 1, "tmax": 500 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-video.json b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-video.json index 949e74602dd..147b353cbb2 100644 --- a/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-video.json +++ b/adapters/zeroclickfraud/zeroclickfraudtest/exemplary/simple-video.json @@ -74,7 +74,8 @@ }, "at": 1, "tmax": 500 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-response-body.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-response-body.json index 87ad168467d..9e1483c79ec 100644 --- a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-response-body.json +++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-response-body.json @@ -71,7 +71,8 @@ }, "at": 1, "tmax": 500 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { @@ -81,7 +82,7 @@ }], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "value": "expect { or n, but found \"", "comparison": "literal" } ] diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-server-response.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-server-response.json index fdea4f109a7..5420e91d56c 100644 --- a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-server-response.json +++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-server-response.json @@ -71,7 +71,8 @@ }, "at": 1, "tmax": 500 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-ext.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-ext.json index 68d29e880b9..9e72e3cb6e5 100644 --- a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-ext.json +++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-ext.json @@ -18,8 +18,8 @@ "expectedMakeRequestsErrors": [ { - "value": "Missing bidder ext: unexpected end of JSON input", - "comparison": "literal" + "value": "Missing bidder ext: expect { or n, but found", + "comparison": "startswith" } ] diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-extparam.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-extparam.json index d272cd5347c..7755ef80ec2 100644 --- a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-extparam.json +++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/missing-extparam.json @@ -21,8 +21,8 @@ "expectedMakeRequestsErrors": [ { - "value": "Cannot Resolve host or sourceId: unexpected end of JSON input", - "comparison": "literal" + "value": "Cannot Resolve host or sourceId: expect { or n, but found", + "comparison": "startswith" } ] diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/no-content-response.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/no-content-response.json index 3a36d6e04b2..eb78a456c3f 100644 --- a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/no-content-response.json +++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/no-content-response.json @@ -71,7 +71,8 @@ }, "at": 1, "tmax": 500 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/banner.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/banner.json index cdfaf37e45d..3b0918e6858 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/banner.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/banner.json @@ -44,7 +44,7 @@ "application/json;charset=utf-8" ] }, - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "body": { "id": "some-request-id", "imp": [ @@ -78,7 +78,8 @@ }, "test": 1, "tmax": 500 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/no-bid.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/no-bid.json index 68aabbed257..ebb85839138 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/no-bid.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/no-bid.json @@ -44,7 +44,7 @@ "application/json;charset=utf-8" ] }, - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "body": { "id": "some-request-id", "imp": [ @@ -78,7 +78,8 @@ }, "test": 1, "tmax": 500 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 204 diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/video.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/video.json index 248f4b0487a..2677ea5f1ca 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/video.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/exemplary/video.json @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "headers": { "Content-Type": [ "application/json;charset=utf-8" @@ -74,7 +74,8 @@ "h": 900 }, "test": 1 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/bad-request.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/bad-request.json index 38f3bd326d0..88215a085a3 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/bad-request.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/bad-request.json @@ -44,7 +44,7 @@ "application/json;charset=utf-8" ] }, - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "body": { "id": "some-request-id", "imp": [ @@ -78,7 +78,8 @@ }, "test": 1, "tmax": 500 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 400 diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/invalid-bid-type.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/invalid-bid-type.json index 4a55670720d..5ed9230e949 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/invalid-bid-type.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/invalid-bid-type.json @@ -44,7 +44,7 @@ "application/json;charset=utf-8" ] }, - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "body": { "id": "some-request-id", "imp": [ @@ -78,7 +78,8 @@ }, "test": 1, "tmax": 500 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/no-bid-type.json.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/no-bid-type.json.json index 1992586435f..f0065941df0 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/no-bid-type.json.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/no-bid-type.json.json @@ -44,7 +44,7 @@ "application/json;charset=utf-8" ] }, - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "body": { "id": "some-request-id", "imp": [ @@ -78,7 +78,8 @@ }, "test": 1, "tmax": 500 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 200, diff --git a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/server-error.json b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/server-error.json index 037c7307889..0ec1555ffb7 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/server-error.json +++ b/adapters/zeta_global_ssp/zeta_global_ssp-test/supplemental/server-error.json @@ -44,7 +44,7 @@ "application/json;charset=utf-8" ] }, - "uri": "http://whatever.url", + "uri": "https://ssp.disqus.com/bid/prebid-server?sid=11", "body": { "id": "some-request-id", "imp": [ @@ -78,7 +78,8 @@ }, "test": 1, "tmax": 500 - } + }, + "impIDs":["some-impression-id"] }, "mockResponse": { "status": 500 diff --git a/adapters/zeta_global_ssp/zeta_global_ssp.go b/adapters/zeta_global_ssp/zeta_global_ssp.go index 7a5f3395724..e06c6f05eff 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp.go +++ b/adapters/zeta_global_ssp/zeta_global_ssp.go @@ -5,11 +5,12 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type adapter struct { @@ -38,6 +39,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte Uri: a.endpoint, Body: requestJson, Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), } return []*adapters.RequestData{requestData}, nil @@ -53,7 +55,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R } var response openrtb2.BidResponse - if err := json.Unmarshal(responseData.Body, &response); err != nil { + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { return nil, []error{err} } @@ -80,7 +82,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { if bid.Ext != nil { var bidExt openrtb_ext.ExtBid - err := json.Unmarshal(bid.Ext, &bidExt) + err := jsonutil.Unmarshal(bid.Ext, &bidExt) if err == nil && bidExt.Prebid != nil { return openrtb_ext.ParseBidType(string(bidExt.Prebid.Type)) } diff --git a/adapters/zeta_global_ssp/zeta_global_ssp_test.go b/adapters/zeta_global_ssp/zeta_global_ssp_test.go index 3b7be288fa1..f2b51409165 100644 --- a/adapters/zeta_global_ssp/zeta_global_ssp_test.go +++ b/adapters/zeta_global_ssp/zeta_global_ssp_test.go @@ -3,14 +3,14 @@ package zeta_global_ssp import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderZetaGlobalSsp, config.Adapter{ - Endpoint: "http://whatever.url"}, + Endpoint: "https://ssp.disqus.com/bid/prebid-server?sid=11"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { diff --git a/adapters/zmaticoo/params_test.go b/adapters/zmaticoo/params_test.go new file mode 100644 index 00000000000..c6c462155aa --- /dev/null +++ b/adapters/zmaticoo/params_test.go @@ -0,0 +1,48 @@ +package zmaticoo + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderZmaticoo, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected zmaticoo params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the zmaticoo schema rejects all the imp.ext fields we don't support. +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderZmaticoo, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"pubId": "11233", "zoneId": "sin"}`, + `{"pubId": "11244", "zoneId": "iad"}`, +} + +var invalidParams = []string{ + `{"pubId": "11233"}`, + `{"zoneId": "aaa"}`, + `{"pubId": 123, "zoneId": "sin"}`, + `{"pubId": "", "zoneId": "iad"}`, + `{"pubId": "11233", "zoneId": ""}`, +} diff --git a/adapters/zmaticoo/zmaticoo.go b/adapters/zmaticoo/zmaticoo.go new file mode 100644 index 00000000000..9e0da9e449a --- /dev/null +++ b/adapters/zmaticoo/zmaticoo.go @@ -0,0 +1,152 @@ +package zmaticoo + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +type adapter struct { + endpoint string +} + +// Builder builds a new instance of the zmaticoo adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + return &adapter{ + endpoint: config.Endpoint, + }, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + adapterRequest, errs := a.makeRequest(request) + if errs != nil { + return nil, errs + } + return []*adapters.RequestData{adapterRequest}, nil + +} + +func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { + errs := validateZmaticooExt(request) + if errs != nil { + return nil, errs + } + err := transform(request) + if err != nil { + return nil, append(errs, err) + } + reqBody, err := json.Marshal(request) + if err != nil { + return nil, append(errs, err) + } + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + return &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: reqBody, + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), + }, errs +} + +func transform(request *openrtb2.BidRequest) error { + for i, imp := range request.Imp { + if imp.Native != nil { + var nativeRequest map[string]interface{} + nativeCopyRequest := make(map[string]interface{}) + if err := jsonutil.Unmarshal([]byte(request.Imp[i].Native.Request), &nativeRequest); err != nil { + return err + } + _, exists := nativeRequest["native"] + if exists { + continue + } + nativeCopyRequest["native"] = nativeRequest + nativeReqByte, err := json.Marshal(nativeCopyRequest) + if err != nil { + return err + } + nativeCopy := *request.Imp[i].Native + nativeCopy.Request = string(nativeReqByte) + request.Imp[i].Native = &nativeCopy + } + } + return nil +} + +func validateZmaticooExt(request *openrtb2.BidRequest) []error { + var extImpZmaticoo openrtb_ext.ExtImpZmaticoo + var errs []error + for _, imp := range request.Imp { + var extBidder adapters.ExtImpBidder + err := jsonutil.Unmarshal(imp.Ext, &extBidder) + if err != nil { + errs = append(errs, err) + continue + } + err = jsonutil.Unmarshal(extBidder.Bidder, &extImpZmaticoo) + if err != nil { + errs = append(errs, err) + continue + } + if extImpZmaticoo.ZoneId == "" || extImpZmaticoo.PubId == "" { + errs = append(errs, fmt.Errorf("imp.ext.pubId or imp.ext.zoneId required")) + continue + } + } + return errs + +} + +// MakeBids make the bids for the bid response. +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if adapters.IsResponseStatusCodeNoContent(response) { + return nil, nil + } + if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: %d.", response.StatusCode), + }} + } + var bidResp openrtb2.BidResponse + if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + var errs []error + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(internalRequest.Imp)) + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + mediaType, err := getMediaTypeForBid(sb.Bid[i]) + if err != nil { + errs = append(errs, err) + continue + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: mediaType, + }) + } + } + return bidResponse, errs +} + +func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + default: + return "", fmt.Errorf("unrecognized bid type in response from zmaticoo for bid %s", bid.ImpID) + } +} diff --git a/adapters/zmaticoo/zmaticoo_test.go b/adapters/zmaticoo/zmaticoo_test.go new file mode 100644 index 00000000000..df12c1e1b6f --- /dev/null +++ b/adapters/zmaticoo/zmaticoo_test.go @@ -0,0 +1,20 @@ +package zmaticoo + +import ( + "testing" + + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderZmaticoo, config.Adapter{ + Endpoint: "https://bid.zmaticoo.com/prebid/bid"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "zmaticootest", bidder) +} diff --git a/adapters/zmaticoo/zmaticootest/exemplary/no-bid.json b/adapters/zmaticoo/zmaticootest/exemplary/no-bid.json new file mode 100644 index 00000000000..5f5712412f5 --- /dev/null +++ b/adapters/zmaticoo/zmaticootest/exemplary/no-bid.json @@ -0,0 +1,59 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.zmaticoo.com/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/zmaticoo/zmaticootest/exemplary/simple-banner.json b/adapters/zmaticoo/zmaticootest/exemplary/simple-banner.json new file mode 100644 index 00000000000..c9755d903e6 --- /dev/null +++ b/adapters/zmaticoo/zmaticootest/exemplary/simple-banner.json @@ -0,0 +1,94 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.zmaticoo.com/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "ttx", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 1.2, + "adm": "some-ads", + "crid": "crid_testid", + "mtype": 1 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 1.2, + "adm": "some-ads", + "crid": "crid_testid", + "mtype": 1 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/zmaticoo/zmaticootest/exemplary/simple-native-1.1.json b/adapters/zmaticoo/zmaticootest/exemplary/simple-native-1.1.json new file mode 100644 index 00000000000..7f6ad6fce11 --- /dev/null +++ b/adapters/zmaticoo/zmaticootest/exemplary/simple-native-1.1.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"native\":{\"ver\":\"1.2\",\"context\":1,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":2,\"required\":1,\"title\":{\"len\":90}},{\"id\":6,\"required\":1,\"img\":{\"type\":3,\"wmin\":128,\"hmin\":128,\"mimes\":[\"image/jpg\",\"image/jpeg\",\"image/png\"]}},{\"id\":7,\"required\":1,\"data\":{\"type\":2,\"len\":120}}]}}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://bid.zmaticoo.com/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"native\":{\"ver\":\"1.2\",\"context\":1,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":2,\"required\":1,\"title\":{\"len\":90}},{\"id\":6,\"required\":1,\"img\":{\"type\":3,\"wmin\":128,\"hmin\":128,\"mimes\":[\"image/jpg\",\"image/jpeg\",\"image/png\"]}},{\"id\":7,\"required\":1,\"data\":{\"type\":2,\"len\":120}}]}}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "8400d766-58b3-47d4-80d7-6658b337d403", + "impid": "test-imp-id", + "price": 1.2, + "adm": "some ads", + "crid": "crid_testid", + "mtype": 4 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8400d766-58b3-47d4-80d7-6658b337d403", + "impid": "test-imp-id", + "price": 1.2, + "adm": "some ads", + "crid": "crid_testid", + "mtype": 4 + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/zmaticoo/zmaticootest/exemplary/simple-native.json b/adapters/zmaticoo/zmaticootest/exemplary/simple-native.json new file mode 100644 index 00000000000..baf66759c83 --- /dev/null +++ b/adapters/zmaticoo/zmaticootest/exemplary/simple-native.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"ver\":\"1.2\",\"context\":1,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":2,\"required\":1,\"title\":{\"len\":90}},{\"id\":6,\"required\":1,\"img\":{\"type\":3,\"wmin\":128,\"hmin\":128,\"mimes\":[\"image/jpg\",\"image/jpeg\",\"image/png\"]}},{\"id\":7,\"required\":1,\"data\":{\"type\":2,\"len\":120}}]}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://bid.zmaticoo.com/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"native\":{\"assets\":[{\"id\":2,\"required\":1,\"title\":{\"len\":90}},{\"id\":6,\"img\":{\"hmin\":128,\"mimes\":[\"image/jpg\",\"image/jpeg\",\"image/png\"],\"type\":3,\"wmin\":128},\"required\":1},{\"data\":{\"len\":120,\"type\":2},\"id\":7,\"required\":1}],\"context\":1,\"plcmtcnt\":1,\"plcmttype\":4,\"ver\":\"1.2\"}}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "8400d766-58b3-47d4-80d7-6658b337d403", + "impid": "test-imp-id", + "price": 1.2, + "adm": "some ads", + "crid": "crid_testid", + "mtype": 4 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8400d766-58b3-47d4-80d7-6658b337d403", + "impid": "test-imp-id", + "price": 1.2, + "adm": "some ads", + "crid": "crid_testid", + "mtype": 4 + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/zmaticoo/zmaticootest/exemplary/simple-video.json b/adapters/zmaticoo/zmaticootest/exemplary/simple-video.json new file mode 100644 index 00000000000..718eaa76477 --- /dev/null +++ b/adapters/zmaticoo/zmaticootest/exemplary/simple-video.json @@ -0,0 +1,92 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.zmaticoo.com/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "ttx", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 1.2, + "adm": "some-ads", + "crid": "crid_testid", + "mtype": 2 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 1.2, + "adm": "some-ads", + "crid": "crid_testid", + "mtype": 2 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/zmaticoo/zmaticootest/supplemental/bad_imp_ext.json b/adapters/zmaticoo/zmaticootest/supplemental/bad_imp_ext.json new file mode 100644 index 00000000000..782f7a0c3b4 --- /dev/null +++ b/adapters/zmaticoo/zmaticootest/supplemental/bad_imp_ext.json @@ -0,0 +1,21 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 300, "h": 50}] + }, + "ext": "aaa" + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "expect { or n, but found \"", + "comparison": "literal" + } + ] +} diff --git a/adapters/zmaticoo/zmaticootest/supplemental/bad_imp_ext_bidder.json b/adapters/zmaticoo/zmaticootest/supplemental/bad_imp_ext_bidder.json new file mode 100644 index 00000000000..b9a32be2ce6 --- /dev/null +++ b/adapters/zmaticoo/zmaticootest/supplemental/bad_imp_ext_bidder.json @@ -0,0 +1,27 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": "aa" + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "expect { or n, but found \"", + "comparison": "literal" + } + ] +} diff --git a/adapters/zmaticoo/zmaticootest/supplemental/bad_response.json b/adapters/zmaticoo/zmaticootest/supplemental/bad_response.json new file mode 100644 index 00000000000..3d053597a20 --- /dev/null +++ b/adapters/zmaticoo/zmaticootest/supplemental/bad_response.json @@ -0,0 +1,64 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.zmaticoo.com/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": "{\"id\":test-request-id" + } + } + ], + "expectedMakeBidsErrors": [ + { + "comparison": "literal", + "value": "expect { or n, but found \"" + } + ] +} diff --git a/adapters/triplelift_native/triplelift_nativetest/supplemental/nopub.json b/adapters/zmaticoo/zmaticootest/supplemental/empty_imp_ext_bidder.json similarity index 52% rename from adapters/triplelift_native/triplelift_nativetest/supplemental/nopub.json rename to adapters/zmaticoo/zmaticootest/supplemental/empty_imp_ext_bidder.json index cdeaff3961a..43ee7a7214c 100644 --- a/adapters/triplelift_native/triplelift_nativetest/supplemental/nopub.json +++ b/adapters/zmaticoo/zmaticootest/supplemental/empty_imp_ext_bidder.json @@ -1,13 +1,6 @@ { - "expectedMakeRequestsErrors": [ - { - "value": "Unsupported publisher for triplelift_native", - "comparison": "literal" - } - ], "mockBidRequest": { "id": "test-request-id", - "app": { "publisher": {"ext":{"prebid":{"parentAccount":"faz"}}, "id":"far","name":"bar"}}, "imp": [ { "id": "test-imp-id", @@ -15,23 +8,23 @@ "format": [ { "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 + "h": 50 } ] }, "ext": { "bidder": { - "inventoryCode": "foo", - "floor": 20 + "pubId": "", + "zoneId": "" } } - } + } ] }, - "httpCalls": [ + "expectedMakeRequestsErrors": [ + { + "value": "imp.ext.pubId or imp.ext.zoneId required", + "comparison": "literal" + } ] } diff --git a/adapters/zmaticoo/zmaticootest/supplemental/status_400.json b/adapters/zmaticoo/zmaticootest/supplemental/status_400.json new file mode 100644 index 00000000000..fdfbd759735 --- /dev/null +++ b/adapters/zmaticoo/zmaticootest/supplemental/status_400.json @@ -0,0 +1,64 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.zmaticoo.com/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "comparison": "literal", + "value": "Unexpected status code: 400." + } + ] +} diff --git a/adapters/zmaticoo/zmaticootest/supplemental/status_500.json b/adapters/zmaticoo/zmaticootest/supplemental/status_500.json new file mode 100644 index 00000000000..4cbe06d1eda --- /dev/null +++ b/adapters/zmaticoo/zmaticootest/supplemental/status_500.json @@ -0,0 +1,64 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.zmaticoo.com/prebid/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "pubId": "fake-pub-id", + "zoneId": "sin" + } + } + } + ] + }, + "impIDs":["test-imp-id"] + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "comparison": "literal", + "value": "Unexpected status code: 500." + } + ] +} diff --git a/adservertargeting/adservertargeting.go b/adservertargeting/adservertargeting.go index 94d64579e66..3ab7cefe6c4 100644 --- a/adservertargeting/adservertargeting.go +++ b/adservertargeting/adservertargeting.go @@ -6,8 +6,8 @@ import ( "strings" "github.com/buger/jsonparser" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) type DataSource string diff --git a/adservertargeting/adservertargeting_test.go b/adservertargeting/adservertargeting_test.go index 0da3635d9b7..8ba3c256b3d 100644 --- a/adservertargeting/adservertargeting_test.go +++ b/adservertargeting/adservertargeting_test.go @@ -5,10 +5,10 @@ import ( "net/url" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/openrtb/v19/openrtb3" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/openrtb/v20/openrtb3" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/adservertargeting/reqcache.go b/adservertargeting/reqcache.go index b8b147af84e..03c87f024cb 100644 --- a/adservertargeting/reqcache.go +++ b/adservertargeting/reqcache.go @@ -4,8 +4,8 @@ import ( "encoding/json" "github.com/buger/jsonparser" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type requestCache struct { diff --git a/adservertargeting/requestcache_test.go b/adservertargeting/requestcache_test.go index cbafe0f44eb..248f11a8441 100644 --- a/adservertargeting/requestcache_test.go +++ b/adservertargeting/requestcache_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" "github.com/stretchr/testify/assert" ) diff --git a/adservertargeting/requestlookup.go b/adservertargeting/requestlookup.go index debf19830db..31c1a9d939e 100644 --- a/adservertargeting/requestlookup.go +++ b/adservertargeting/requestlookup.go @@ -3,11 +3,12 @@ package adservertargeting import ( "encoding/json" "fmt" - "github.com/buger/jsonparser" - "github.com/pkg/errors" - "github.com/prebid/prebid-server/openrtb_ext" "net/url" "strings" + + "github.com/buger/jsonparser" + "github.com/pkg/errors" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func getAdServerTargeting(reqWrapper *openrtb_ext.RequestWrapper) ([]openrtb_ext.AdServerTarget, error) { diff --git a/adservertargeting/requestlookup_test.go b/adservertargeting/requestlookup_test.go index cd86364558e..22f914ac793 100644 --- a/adservertargeting/requestlookup_test.go +++ b/adservertargeting/requestlookup_test.go @@ -5,8 +5,8 @@ import ( "net/url" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adservertargeting/respdataprocessor.go b/adservertargeting/respdataprocessor.go index 649f802f6e2..4b18d64b44b 100644 --- a/adservertargeting/respdataprocessor.go +++ b/adservertargeting/respdataprocessor.go @@ -6,9 +6,9 @@ import ( "strings" "github.com/pkg/errors" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" ) @@ -82,13 +82,13 @@ func buildBidExt(targetingData map[string]string, } bidExtTargeting, err := jsonutil.Marshal(bidExtTargetingData) if err != nil { - warnings = append(warnings, createWarning(err.Error())) + warnings = append(warnings, createWarning(err.Error())) //nolint: ineffassign,staticcheck return nil } newExt, err := jsonpatch.MergePatch(bid.Ext, bidExtTargeting) if err != nil { - warnings = append(warnings, createWarning(err.Error())) + warnings = append(warnings, createWarning(err.Error())) //nolint: ineffassign,staticcheck return nil } return newExt diff --git a/adservertargeting/respdataprocessor_test.go b/adservertargeting/respdataprocessor_test.go index 1118458e0f6..973f9b801e5 100644 --- a/adservertargeting/respdataprocessor_test.go +++ b/adservertargeting/respdataprocessor_test.go @@ -6,9 +6,9 @@ import ( "strings" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/openrtb/v19/openrtb3" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/openrtb/v20/openrtb3" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adservertargeting/utils.go b/adservertargeting/utils.go index 8093a4b6974..dd70bf1b667 100644 --- a/adservertargeting/utils.go +++ b/adservertargeting/utils.go @@ -1,11 +1,12 @@ package adservertargeting import ( + "strings" + "github.com/buger/jsonparser" "github.com/pkg/errors" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "strings" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func splitAndGet(path string, data []byte, delimiter string) (string, error) { diff --git a/amp/parse.go b/amp/parse.go index 34f1a3cacb4..2a86bc7b6b7 100644 --- a/amp/parse.go +++ b/amp/parse.go @@ -9,11 +9,11 @@ import ( tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/privacy" + "github.com/prebid/prebid-server/v3/privacy/ccpa" + "github.com/prebid/prebid-server/v3/privacy/gdpr" ) // Params defines the parameters of an AMP request. @@ -73,7 +73,7 @@ func ReadPolicy(ampParams Params, pbsConfigGDPREnabled bool) (privacy.PolicyWrit warningMsg = validateTCf2ConsentString(ampParams.Consent) } case ConsentUSPrivacy: - rv = ccpa.ConsentWriter{ampParams.Consent} + rv = ccpa.ConsentWriter{Consent: ampParams.Consent} if ccpa.ValidateConsent(ampParams.Consent) { if parseGdprApplies(ampParams.GdprApplies) == 1 { // Log warning because AMP request comes with both a valid CCPA string and gdpr_applies set to true @@ -85,7 +85,7 @@ func ReadPolicy(ampParams Params, pbsConfigGDPREnabled bool) (privacy.PolicyWrit } default: if ccpa.ValidateConsent(ampParams.Consent) { - rv = ccpa.ConsentWriter{ampParams.Consent} + rv = ccpa.ConsentWriter{Consent: ampParams.Consent} if parseGdprApplies(ampParams.GdprApplies) == 1 { warningMsg = "AMP request gdpr_applies value was ignored because provided consent string is a CCPA consent string" } @@ -117,7 +117,7 @@ func buildGdprTCF2ConsentWriter(ampParams Params) gdpr.ConsentWriter { // set regs.ext.gdpr if non-nil gdpr_applies was set to true gdprValue = parseGdprApplies(ampParams.GdprApplies) } - writer.RegExtGDPR = &gdprValue + writer.GDPR = &gdprValue return writer } diff --git a/amp/parse_test.go b/amp/parse_test.go index 9f981fd30e0..65b69012b45 100644 --- a/amp/parse_test.go +++ b/amp/parse_test.go @@ -4,11 +4,11 @@ import ( "net/http" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/privacy" + "github.com/prebid/prebid-server/v3/privacy/ccpa" + "github.com/prebid/prebid-server/v3/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -284,18 +284,24 @@ func TestPrivacyReader(t *testing.T) { ampParams: Params{Consent: "1YYY"}, }, expected: expectedResults{ - policyWriter: ccpa.ConsentWriter{"1YYY"}, + policyWriter: ccpa.ConsentWriter{Consent: "1YYY"}, warning: nil, }, }, { desc: "No consent type, valid CCPA consent string and gdpr_applies set to true: expect a CCPA consent writer and a warning", in: testInput{ - ampParams: Params{Consent: "1YYY", GdprApplies: &boolTrue}, + ampParams: Params{ + Consent: "1YYY", + GdprApplies: &boolTrue, + }, }, expected: expectedResults{ - policyWriter: ccpa.ConsentWriter{"1YYY"}, - warning: &errortypes.Warning{Message: "AMP request gdpr_applies value was ignored because provided consent string is a CCPA consent string", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, + policyWriter: ccpa.ConsentWriter{Consent: "1YYY"}, + warning: &errortypes.Warning{ + Message: "AMP request gdpr_applies value was ignored because provided consent string is a CCPA consent string", + WarningCode: errortypes.InvalidPrivacyConsentWarningCode, + }, }, }, { @@ -304,8 +310,11 @@ func TestPrivacyReader(t *testing.T) { ampParams: Params{Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA"}, }, expected: expectedResults{ - policyWriter: gdpr.ConsentWriter{"CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", &int8One}, - warning: nil, + policyWriter: gdpr.ConsentWriter{ + Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", + GDPR: &int8One, + }, + warning: nil, }, }, }, @@ -316,41 +325,63 @@ func TestPrivacyReader(t *testing.T) { { desc: "Unrecognized consent type was specified and invalid consent string provided: expect nil policy writer and a warning", in: testInput{ - ampParams: Params{ConsentType: 101, Consent: "NOT_CCPA_NOR_GDPR_TCF2"}, + ampParams: Params{ + ConsentType: 101, + Consent: "NOT_CCPA_NOR_GDPR_TCF2", + }, }, expected: expectedResults{ policyWriter: privacy.NilPolicyWriter{}, - warning: &errortypes.Warning{Message: "Consent string 'NOT_CCPA_NOR_GDPR_TCF2' is not recognized as one of the supported formats CCPA or TCF2.", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, + warning: &errortypes.Warning{ + Message: "Consent string 'NOT_CCPA_NOR_GDPR_TCF2' is not recognized as one of the supported formats CCPA or TCF2.", + WarningCode: errortypes.InvalidPrivacyConsentWarningCode, + }, }, }, { desc: "Unrecognized consent type specified but query params come with a valid CCPA consent string: expect a CCPA consent writer and no error nor warning", in: testInput{ - ampParams: Params{ConsentType: 101, Consent: "1YYY"}, + ampParams: Params{ + ConsentType: 101, + Consent: "1YYY", + }, }, expected: expectedResults{ - policyWriter: ccpa.ConsentWriter{"1YYY"}, + policyWriter: ccpa.ConsentWriter{Consent: "1YYY"}, warning: nil, }, }, { desc: "Unrecognized consent type, valid CCPA consent string and gdpr_applies set to true: expect a CCPA consent writer and a warning", in: testInput{ - ampParams: Params{ConsentType: 101, Consent: "1YYY", GdprApplies: &boolTrue}, + ampParams: Params{ + ConsentType: 101, + Consent: "1YYY", + GdprApplies: &boolTrue, + }, }, expected: expectedResults{ - policyWriter: ccpa.ConsentWriter{"1YYY"}, - warning: &errortypes.Warning{Message: "AMP request gdpr_applies value was ignored because provided consent string is a CCPA consent string", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, + policyWriter: ccpa.ConsentWriter{Consent: "1YYY"}, + warning: &errortypes.Warning{ + Message: "AMP request gdpr_applies value was ignored because provided consent string is a CCPA consent string", + WarningCode: errortypes.InvalidPrivacyConsentWarningCode, + }, }, }, { desc: "Unrecognized consent type, valid TCF2 consent string and gdpr_applies not set: expect GDPR consent writer and no error nor warning", in: testInput{ - ampParams: Params{ConsentType: 101, Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA"}, + ampParams: Params{ + ConsentType: 101, + Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", + }, }, expected: expectedResults{ - policyWriter: gdpr.ConsentWriter{"CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", &int8One}, - warning: nil, + policyWriter: gdpr.ConsentWriter{ + Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", + GDPR: &int8One, + }, + warning: nil, }, }, }, @@ -361,51 +392,91 @@ func TestPrivacyReader(t *testing.T) { { desc: "GDPR consent string is invalid, but consent type is TCF2: return a valid GDPR writer and warn about the GDPR string being invalid", in: testInput{ - ampParams: Params{Consent: "INVALID_GDPR", ConsentType: ConsentTCF2, GdprApplies: nil}, + ampParams: Params{ + Consent: "INVALID_GDPR", + ConsentType: ConsentTCF2, + GdprApplies: nil, + }, }, expected: expectedResults{ - policyWriter: gdpr.ConsentWriter{"INVALID_GDPR", &int8One}, - warning: &errortypes.Warning{Message: "Consent string 'INVALID_GDPR' is not a valid TCF2 consent string.", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, + policyWriter: gdpr.ConsentWriter{ + Consent: "INVALID_GDPR", + GDPR: &int8One, + }, + warning: &errortypes.Warning{ + Message: "Consent string 'INVALID_GDPR' is not a valid TCF2 consent string.", + WarningCode: errortypes.InvalidPrivacyConsentWarningCode, + }, }, }, { desc: "GDPR consent string is invalid, consent type is TCF2, gdpr_applies is set to true: return a valid GDPR writer and warn about the GDPR string being invalid", in: testInput{ - ampParams: Params{Consent: "INVALID_GDPR", ConsentType: ConsentTCF2, GdprApplies: &boolFalse}, + ampParams: Params{ + Consent: "INVALID_GDPR", + ConsentType: ConsentTCF2, + GdprApplies: &boolFalse, + }, }, expected: expectedResults{ - policyWriter: gdpr.ConsentWriter{"INVALID_GDPR", &int8Zero}, - warning: &errortypes.Warning{Message: "Consent string 'INVALID_GDPR' is not a valid TCF2 consent string.", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, + policyWriter: gdpr.ConsentWriter{ + Consent: "INVALID_GDPR", + GDPR: &int8Zero, + }, + warning: &errortypes.Warning{ + Message: "Consent string 'INVALID_GDPR' is not a valid TCF2 consent string.", + WarningCode: errortypes.InvalidPrivacyConsentWarningCode, + }, }, }, { desc: "Valid GDPR consent string, gdpr_applies is set to false, return a valid GDPR writer, no warning", in: testInput{ - ampParams: Params{Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", ConsentType: ConsentTCF2, GdprApplies: &boolFalse}, + ampParams: Params{ + Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", + ConsentType: ConsentTCF2, + GdprApplies: &boolFalse, + }, }, expected: expectedResults{ - policyWriter: gdpr.ConsentWriter{"CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", &int8Zero}, - warning: nil, + policyWriter: gdpr.ConsentWriter{ + Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", + GDPR: &int8Zero, + }, + warning: nil, }, }, { desc: "Valid GDPR consent string, gdpr_applies is set to true, return a valid GDPR writer and no warning", in: testInput{ - ampParams: Params{Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", ConsentType: ConsentTCF2, GdprApplies: &boolTrue}, + ampParams: Params{ + Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", + ConsentType: ConsentTCF2, + GdprApplies: &boolTrue, + }, }, expected: expectedResults{ - policyWriter: gdpr.ConsentWriter{"CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", &int8One}, - warning: nil, + policyWriter: gdpr.ConsentWriter{ + Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", + GDPR: &int8One, + }, + warning: nil, }, }, { desc: "Valid GDPR consent string, return a valid GDPR writer and no warning", in: testInput{ - ampParams: Params{Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", ConsentType: ConsentTCF2}, + ampParams: Params{ + Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", + ConsentType: ConsentTCF2, + }, }, expected: expectedResults{ - policyWriter: gdpr.ConsentWriter{"CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", &int8One}, - warning: nil, + policyWriter: gdpr.ConsentWriter{ + Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", + GDPR: &int8One, + }, + warning: nil, }, }, }, @@ -416,30 +487,46 @@ func TestPrivacyReader(t *testing.T) { { desc: "CCPA consent string is invalid: return a valid writer a warning about the string being invalid", in: testInput{ - ampParams: Params{Consent: "XXXX", ConsentType: ConsentUSPrivacy}, + ampParams: Params{ + Consent: "XXXX", + ConsentType: ConsentUSPrivacy, + }, }, expected: expectedResults{ - policyWriter: ccpa.ConsentWriter{"XXXX"}, - warning: &errortypes.Warning{Message: "Consent string 'XXXX' is not a valid CCPA consent string.", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, + policyWriter: ccpa.ConsentWriter{Consent: "XXXX"}, + warning: &errortypes.Warning{ + Message: "Consent string 'XXXX' is not a valid CCPA consent string.", + WarningCode: errortypes.InvalidPrivacyConsentWarningCode, + }, }, }, { desc: "Valid CCPA consent string, gdpr_applies is set to true: return a valid GDPR writer and warn about the gdpr_applies value.", in: testInput{ - ampParams: Params{Consent: "1YYY", ConsentType: ConsentUSPrivacy, GdprApplies: &boolTrue}, + ampParams: Params{ + Consent: "1YYY", + ConsentType: ConsentUSPrivacy, + GdprApplies: &boolTrue, + }, }, expected: expectedResults{ - policyWriter: ccpa.ConsentWriter{"1YYY"}, - warning: &errortypes.Warning{Message: "AMP request gdpr_applies value was ignored because provided consent string is a CCPA consent string", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}, + policyWriter: ccpa.ConsentWriter{Consent: "1YYY"}, + warning: &errortypes.Warning{ + Message: "AMP request gdpr_applies value was ignored because provided consent string is a CCPA consent string", + WarningCode: errortypes.InvalidPrivacyConsentWarningCode, + }, }, }, { desc: "Valid CCPA consent string, return a valid GDPR writer and no warning", in: testInput{ - ampParams: Params{Consent: "1YYY", ConsentType: ConsentUSPrivacy}, + ampParams: Params{ + Consent: "1YYY", + ConsentType: ConsentUSPrivacy, + }, }, expected: expectedResults{ - policyWriter: ccpa.ConsentWriter{"1YYY"}, + policyWriter: ccpa.ConsentWriter{Consent: "1YYY"}, warning: nil, }, }, @@ -469,19 +556,34 @@ func TestBuildGdprTCF2ConsentWriter(t *testing.T) { expectedWriter gdpr.ConsentWriter }{ { - desc: "gdpr_applies not set", - inParams: Params{Consent: consentString}, - expectedWriter: gdpr.ConsentWriter{consentString, &int8One}, + desc: "gdpr_applies not set", + inParams: Params{Consent: consentString}, + expectedWriter: gdpr.ConsentWriter{ + Consent: consentString, + GDPR: &int8One, + }, }, { - desc: "gdpr_applies set to false", - inParams: Params{Consent: consentString, GdprApplies: &boolFalse}, - expectedWriter: gdpr.ConsentWriter{consentString, &int8Zero}, + desc: "gdpr_applies set to false", + inParams: Params{ + Consent: consentString, + GdprApplies: &boolFalse, + }, + expectedWriter: gdpr.ConsentWriter{ + Consent: consentString, + GDPR: &int8Zero, + }, }, { - desc: "gdpr_applies set to true", - inParams: Params{Consent: consentString, GdprApplies: &boolTrue}, - expectedWriter: gdpr.ConsentWriter{consentString, &int8One}, + desc: "gdpr_applies set to true", + inParams: Params{ + Consent: consentString, + GdprApplies: &boolTrue, + }, + expectedWriter: gdpr.ConsentWriter{ + Consent: consentString, + GDPR: &int8One, + }, }, } for _, tc := range testCases { diff --git a/analytics/agma/README.md b/analytics/agma/README.md new file mode 100644 index 00000000000..430001863ac --- /dev/null +++ b/analytics/agma/README.md @@ -0,0 +1,28 @@ +# agma Analytics + +In order to use the Agma Analytics Adapter, please adjust the accounts / endpoint with the data provided by agma (https://www.agma-mmc.de). + +## Configuration + +```yaml +analytics: + agma: + # Required: enable the module + enabled: true + # Required: set the accounts you want to track + accounts: + - code: "my-code" # Required: provied by agma + publisher_id: "123" # Required: Exchange specific publisher_id, can be an empty string accounts are not used + site_app_id: "openrtb2-site.id-or-app.id-or-app.bundle" # optional: scope to the publisher with an openrtb2 Site object id or App object id/bundle + # Optional properties (advanced configuration) + endpoint: + url: "https://go.pbs.agma-analytics.de/v1/prebid-server" # Check with agma if your site needs an extra url + timeout: "2s" + gzip: true + buffers: # Flush events when (first condition reached) + # Size of the buffer in bytes + size: "2MB" # greater than 2MB (size using SI standard eg. "44kB", "17MB") + count : 100 # greater than 100 events + timeout: "15m" # greater than 15 minutes (parsed as golang duration) + +``` diff --git a/analytics/agma/agma_module.go b/analytics/agma/agma_module.go new file mode 100644 index 00000000000..6ee16e93b84 --- /dev/null +++ b/analytics/agma/agma_module.go @@ -0,0 +1,271 @@ +package agma + +import ( + "bytes" + "errors" + "net/http" + "os" + "os/signal" + "sync" + "syscall" + "time" + + "github.com/benbjohnson/clock" + "github.com/docker/go-units" + "github.com/golang/glog" + "github.com/prebid/go-gdpr/vendorconsent" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +type httpSender = func(payload []byte) error + +const ( + agmaGVLID = 1122 + p9 = 9 +) + +type AgmaLogger struct { + sender httpSender + clock clock.Clock + accounts []config.AgmaAnalyticsAccount + eventCount int64 + maxEventCount int64 + maxBufferByteSize int64 + maxDuration time.Duration + mux sync.RWMutex + sigTermCh chan os.Signal + buffer bytes.Buffer + bufferCh chan []byte +} + +func newAgmaLogger(cfg config.AgmaAnalytics, sender httpSender, clock clock.Clock) (*AgmaLogger, error) { + pSize, err := units.FromHumanSize(cfg.Buffers.BufferSize) + if err != nil { + return nil, err + } + pDuration, err := time.ParseDuration(cfg.Buffers.Timeout) + if err != nil { + return nil, err + } + if len(cfg.Accounts) == 0 { + return nil, errors.New("Please configure at least one account for Agma Analytics") + } + + buffer := bytes.Buffer{} + buffer.Write([]byte("[")) + + return &AgmaLogger{ + sender: sender, + clock: clock, + accounts: cfg.Accounts, + maxBufferByteSize: pSize, + eventCount: 0, + maxEventCount: int64(cfg.Buffers.EventCount), + maxDuration: pDuration, + buffer: buffer, + bufferCh: make(chan []byte), + sigTermCh: make(chan os.Signal, 1), + }, nil +} + +func NewModule(httpClient *http.Client, cfg config.AgmaAnalytics, clock clock.Clock) (analytics.Module, error) { + sender, err := createHttpSender(httpClient, cfg.Endpoint) + if err != nil { + return nil, err + } + + m, err := newAgmaLogger(cfg, sender, clock) + if err != nil { + return nil, err + } + + signal.Notify(m.sigTermCh, os.Interrupt, syscall.SIGTERM) + + go m.start() + + return m, nil +} + +func (l *AgmaLogger) start() { + ticker := l.clock.Ticker(l.maxDuration) + for { + select { + case <-l.sigTermCh: + glog.Infof("[AgmaAnalytics] Received Close, trying to flush buffer") + l.flush() + return + case event := <-l.bufferCh: + l.bufferEvent(event) + if l.isFull() { + l.flush() + } + case <-ticker.C: + l.flush() + } + } +} + +func (l *AgmaLogger) bufferEvent(data []byte) { + l.mux.Lock() + defer l.mux.Unlock() + + l.buffer.Write(data) + l.buffer.WriteByte(',') + l.eventCount++ +} + +func (l *AgmaLogger) isFull() bool { + l.mux.RLock() + defer l.mux.RUnlock() + return l.eventCount >= l.maxEventCount || int64(l.buffer.Len()) >= l.maxBufferByteSize +} + +func (l *AgmaLogger) flush() { + l.mux.Lock() + + if l.eventCount == 0 || l.buffer.Len() == 0 { + l.mux.Unlock() + return + } + + // Close the json array, remove last , + l.buffer.Truncate(l.buffer.Len() - 1) + l.buffer.Write([]byte("]")) + + payload := make([]byte, l.buffer.Len()) + _, err := l.buffer.Read(payload) + if err != nil { + l.reset() + l.mux.Unlock() + glog.Warning("[AgmaAnalytics] fail to copy the buffer") + return + } + + go l.sender(payload) + + l.reset() + l.mux.Unlock() +} + +func (l *AgmaLogger) reset() { + l.buffer.Reset() + l.buffer.Write([]byte("[")) + l.eventCount = 0 +} + +func (l *AgmaLogger) extractPublisherAndSite(requestWrapper *openrtb_ext.RequestWrapper) (string, string) { + publisherId := "" + appSiteId := "" + if requestWrapper.Site != nil { + if requestWrapper.Site.Publisher != nil { + publisherId = requestWrapper.Site.Publisher.ID + } + appSiteId = requestWrapper.Site.ID + } + if requestWrapper.App != nil { + if requestWrapper.App.Publisher != nil { + publisherId = requestWrapper.App.Publisher.ID + } + appSiteId = requestWrapper.App.ID + if appSiteId == "" { + appSiteId = requestWrapper.App.Bundle + } + + } + return publisherId, appSiteId +} + +func (l *AgmaLogger) shouldTrackEvent(requestWrapper *openrtb_ext.RequestWrapper) (bool, string) { + if requestWrapper.User == nil { + return false, "" + } + consentStr := requestWrapper.User.Consent + + parsedConsent, err := vendorconsent.ParseString(consentStr) + if err != nil { + return false, "" + } + + p9Allowed := parsedConsent.PurposeAllowed(p9) + agmaAllowed := parsedConsent.VendorConsent(agmaGVLID) + if !p9Allowed || !agmaAllowed { + return false, "" + } + + publisherId, appSiteId := l.extractPublisherAndSite(requestWrapper) + if publisherId == "" && appSiteId == "" { + return false, "" + } + + for _, account := range l.accounts { + if account.PublisherId == publisherId { + if account.SiteAppId == "" { + return true, account.Code + } + if account.SiteAppId == appSiteId { + return true, account.Code + } + } + } + + return false, "" +} + +func (l *AgmaLogger) LogAuctionObject(event *analytics.AuctionObject) { + if event == nil || event.Status != http.StatusOK || event.RequestWrapper == nil { + return + } + shouldTrack, code := l.shouldTrackEvent(event.RequestWrapper) + if !shouldTrack { + return + } + data, err := serializeAnayltics(event.RequestWrapper, EventTypeAuction, code, event.StartTime) + if err != nil { + glog.Errorf("[AgmaAnalytics] Error serializing auction object: %v", err) + return + } + l.bufferCh <- data +} + +func (l *AgmaLogger) LogAmpObject(event *analytics.AmpObject) { + if event == nil || event.Status != http.StatusOK || event.RequestWrapper == nil { + return + } + shouldTrack, code := l.shouldTrackEvent(event.RequestWrapper) + if !shouldTrack { + return + } + data, err := serializeAnayltics(event.RequestWrapper, EventTypeAmp, code, event.StartTime) + if err != nil { + glog.Errorf("[AgmaAnalytics] Error serializing amp object: %v", err) + return + } + l.bufferCh <- data +} + +func (l *AgmaLogger) LogVideoObject(event *analytics.VideoObject) { + if event == nil || event.Status != http.StatusOK || event.RequestWrapper == nil { + return + } + shouldTrack, code := l.shouldTrackEvent(event.RequestWrapper) + if !shouldTrack { + return + } + data, err := serializeAnayltics(event.RequestWrapper, EventTypeVideo, code, event.StartTime) + if err != nil { + glog.Errorf("[AgmaAnalytics] Error serializing video object: %v", err) + return + } + l.bufferCh <- data +} + +func (l *AgmaLogger) Shutdown() { + glog.Info("[AgmaAnalytics] Shutdown, trying to flush buffer") + l.flush() // mutex safe +} + +func (l *AgmaLogger) LogCookieSyncObject(event *analytics.CookieSyncObject) {} +func (l *AgmaLogger) LogNotificationEventObject(event *analytics.NotificationEvent) {} +func (l *AgmaLogger) LogSetUIDObject(event *analytics.SetUIDObject) {} diff --git a/analytics/agma/agma_module_test.go b/analytics/agma/agma_module_test.go new file mode 100644 index 00000000000..25f62d6e9e0 --- /dev/null +++ b/analytics/agma/agma_module_test.go @@ -0,0 +1,735 @@ +package agma + +import ( + "io" + "net/http" + "net/http/httptest" + "sync" + "syscall" + "testing" + "time" + + "github.com/benbjohnson/clock" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +var agmaConsent = "CP6-v9RP6-v9RNlAAAENCZCAAICAAAAAAAAAIxQAQIxAAAAA.II7Nd_X__bX9n-_7_6ft0eY1f9_r37uQzDhfNs-8F3L_W_LwX32E7NF36tq4KmR4ku1bBIQNtHMnUDUmxaolVrzHsak2cpyNKJ_JkknsZe2dYGF9Pn9lD-YKZ7_5_9_f52T_9_9_-39z3_9f___dv_-__-vjf_599n_v9fV_78_Kf9______-____________8A" + +var mockValidAuctionObject = analytics.AuctionObject{ + Status: http.StatusOK, + StartTime: time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC), + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "some-id", + Site: &openrtb2.Site{ + ID: "track-me-site", + Publisher: &openrtb2.Publisher{ + ID: "track-me", + }, + }, + Device: &openrtb2.Device{ + UA: "ua", + }, + User: &openrtb2.User{ + Consent: agmaConsent, + }, + }, + }, +} + +var mockValidVideoObject = analytics.VideoObject{ + Status: http.StatusOK, + StartTime: time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC), + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "some-id", + App: &openrtb2.App{ + ID: "track-me-app", + Publisher: &openrtb2.Publisher{ + ID: "track-me", + }, + }, + Device: &openrtb2.Device{ + UA: "ua", + }, + User: &openrtb2.User{ + Consent: agmaConsent, + }, + }, + }, +} + +var mockValidAmpObject = analytics.AmpObject{ + Status: http.StatusOK, + StartTime: time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC), + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "some-id", + Site: &openrtb2.Site{ + ID: "track-me-site", + Publisher: &openrtb2.Publisher{ + ID: "track-me", + }, + }, + Device: &openrtb2.Device{ + UA: "ua", + }, + User: &openrtb2.User{ + Consent: agmaConsent, + }, + }, + }, +} + +var mockValidAccounts = []config.AgmaAnalyticsAccount{ + { + PublisherId: "track-me", + Code: "abc", + SiteAppId: "track-me-app", + }, + { + PublisherId: "track-me", + Code: "abcd", + SiteAppId: "track-me-site", + }, +} + +type MockedSender struct { + mock.Mock +} + +func (m *MockedSender) Send(payload []byte) error { + args := m.Called(payload) + return args.Error(0) +} + +func TestConfigParsingError(t *testing.T) { + testCases := []struct { + name string + config config.AgmaAnalytics + shouldFail bool + }{ + { + name: "Test with invalid/empty URL", + config: config.AgmaAnalytics{ + Enabled: true, + Endpoint: config.AgmaAnalyticsHttpEndpoint{ + Url: "%%2815197306101420000%29", + Timeout: "1s", + Gzip: false, + }, + }, + shouldFail: true, + }, + { + name: "Test with invalid timout", + config: config.AgmaAnalytics{ + Enabled: true, + Endpoint: config.AgmaAnalyticsHttpEndpoint{ + Url: "http://localhost:8000/event", + Timeout: "1x", + Gzip: false, + }, + }, + shouldFail: true, + }, + { + name: "Test with no accounts", + config: config.AgmaAnalytics{ + Enabled: true, + Endpoint: config.AgmaAnalyticsHttpEndpoint{ + Url: "http://localhost:8000/event", + Timeout: "1s", + Gzip: false, + }, + Buffers: config.AgmaAnalyticsBuffer{ + EventCount: 1, + BufferSize: "1Kb", + Timeout: "1s", + }, + Accounts: []config.AgmaAnalyticsAccount{}, + }, + shouldFail: true, + }, + } + clockMock := clock.NewMock() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, err := NewModule(&http.Client{}, tc.config, clockMock) + if tc.shouldFail { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestShouldTrackEvent(t *testing.T) { + cfg := config.AgmaAnalytics{ + Enabled: true, + Endpoint: config.AgmaAnalyticsHttpEndpoint{ + Url: "http://localhost:8000/event", + Timeout: "5s", + }, + Buffers: config.AgmaAnalyticsBuffer{ + EventCount: 1, + BufferSize: "1Kb", + Timeout: "1s", + }, + Accounts: []config.AgmaAnalyticsAccount{ + { + PublisherId: "track-me", + Code: "abc", + }, + { + PublisherId: "", + SiteAppId: "track-me", + Code: "abc", + }, + }, + } + mockedSender := new(MockedSender) + mockedSender.On("Send", mock.Anything).Return(nil) + clockMock := clock.NewMock() + logger, err := newAgmaLogger(cfg, mockedSender.Send, clockMock) + assert.NoError(t, err) + + // no userExt + shouldTrack, code := logger.shouldTrackEvent(&openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "some-id", + App: &openrtb2.App{ + ID: "com.app.test", + Publisher: &openrtb2.Publisher{ + ID: "track-me-not", + }, + }, + User: &openrtb2.User{ + Consent: agmaConsent, + }, + }, + }) + + assert.False(t, shouldTrack) + assert.Equal(t, "", code) + + // no userExt + shouldTrack, code = logger.shouldTrackEvent(&openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{ + ID: "com.app.test", + Publisher: &openrtb2.Publisher{ + ID: "track-me", + }, + }, + }, + }) + + assert.False(t, shouldTrack) + assert.Equal(t, "", code) + + // Constent: No agma + shouldTrack, code = logger.shouldTrackEvent(&openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{ + ID: "com.app.test", + Publisher: &openrtb2.Publisher{ + ID: "track-me", + }, + }, + User: &openrtb2.User{ + Consent: "CP4LywcP4LywcLRAAAENCZCAAAIAAAIAAAAAIxQAQIwgAAAA.II7Nd_X__bX9n-_7_6ft0eY1f9_r37uQzDhfNs-8F3L_W_LwX32E7NF36tq4KmR4ku1bBIQNtHMnUDUmxaolVrzHsak2cpyNKJ_JkknsZe2dYGF9Pn9lD-YKZ7_5_9_f52T_9_9_-39z3_9f___dv_-__-vjf_599n_v9fV_78_Kf9______-____________8A", + }, + }, + }) + + assert.False(t, shouldTrack) + assert.Equal(t, "", code) + + // Constent: No Purpose 9 + shouldTrack, code = logger.shouldTrackEvent(&openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{ + ID: "com.app.test", + Publisher: &openrtb2.Publisher{ + ID: "track-me", + }, + }, + User: &openrtb2.User{ + Consent: "CP4LywcP4LywcLRAAAENCZCAAIAAAAAAAAAAIxQAQIxAAAAA.II7Nd_X__bX9n-_7_6ft0eY1f9_r37uQzDhfNs-8F3L_W_LwX32E7NF36tq4KmR4ku1bBIQNtHMnUDUmxaolVrzHsak2cpyNKJ_JkknsZe2dYGF9Pn9lD-YKZ7_5_9_f52T_9_9_-39z3_9f___dv_-__-vjf_599n_v9fV_78_Kf9______-____________8A", + }, + }, + }) + + assert.False(t, shouldTrack) + assert.Equal(t, "", code) + + // No valid sites / apps / empty publisher app + shouldTrack, code = logger.shouldTrackEvent(&openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{ + ID: "", + Publisher: &openrtb2.Publisher{ + ID: "", + }, + }, + User: &openrtb2.User{ + Consent: agmaConsent, + }, + }, + }) + + assert.False(t, shouldTrack) + assert.Equal(t, "", code) + + // should allow empty accounts + shouldTrack, code = logger.shouldTrackEvent(&openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{ + ID: "track-me", + }, + User: &openrtb2.User{ + Consent: agmaConsent, + }, + }, + }) + + assert.True(t, shouldTrack) + assert.Equal(t, "abc", code) + + // Bundle ID instead of app.id + shouldTrack, code = logger.shouldTrackEvent(&openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{ + Bundle: "track-me", + }, + User: &openrtb2.User{ + Consent: agmaConsent, + }, + }, + }) + + assert.True(t, shouldTrack) + assert.Equal(t, "abc", code) +} + +func TestShouldTrackMultipleAccounts(t *testing.T) { + cfg := config.AgmaAnalytics{ + Enabled: true, + Endpoint: config.AgmaAnalyticsHttpEndpoint{ + Url: "http://localhost:8000/event", + Timeout: "5s", + }, + Buffers: config.AgmaAnalyticsBuffer{ + EventCount: 1, + BufferSize: "1Kb", + Timeout: "1s", + }, + Accounts: []config.AgmaAnalyticsAccount{ + { + PublisherId: "track-me-a", + Code: "abc", + }, + { + PublisherId: "track-me-b", + Code: "123", + }, + }, + } + mockedSender := new(MockedSender) + mockedSender.On("Send", mock.Anything).Return(nil) + clockMock := clock.NewMock() + logger, err := newAgmaLogger(cfg, mockedSender.Send, clockMock) + assert.NoError(t, err) + + shouldTrack, code := logger.shouldTrackEvent(&openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "some-id", + App: &openrtb2.App{ + ID: "com.app.test", + Publisher: &openrtb2.Publisher{ + ID: "track-me-a", + }, + }, + User: &openrtb2.User{ + Consent: agmaConsent, + }, + }, + }) + + assert.True(t, shouldTrack) + assert.Equal(t, "abc", code) + + shouldTrack, code = logger.shouldTrackEvent(&openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "some-id", + Site: &openrtb2.Site{ + ID: "site-test", + Publisher: &openrtb2.Publisher{ + ID: "track-me-b", + }, + }, + User: &openrtb2.User{ + Consent: agmaConsent, + }, + }, + }) + + assert.True(t, shouldTrack) + assert.Equal(t, "123", code) +} + +func TestShouldNotTrackLog(t *testing.T) { + testCases := []struct { + name string + config config.AgmaAnalytics + }{ + { + name: "Test with do-not-track PublisherId", + config: config.AgmaAnalytics{ + Enabled: true, + Endpoint: config.AgmaAnalyticsHttpEndpoint{ + Url: "http://localhost:8000/event", + Timeout: "5s", + }, + Buffers: config.AgmaAnalyticsBuffer{ + EventCount: 1, + BufferSize: "1Kb", + Timeout: "1s", + }, + Accounts: []config.AgmaAnalyticsAccount{ + { + PublisherId: "do-not-track", + Code: "abc", + }, + }, + }, + }, + { + name: "Test with do-not-track PublisherId", + config: config.AgmaAnalytics{ + Enabled: true, + Endpoint: config.AgmaAnalyticsHttpEndpoint{ + Url: "http://localhost:8000/event", + Timeout: "5s", + }, + Buffers: config.AgmaAnalyticsBuffer{ + EventCount: 1, + BufferSize: "1Kb", + Timeout: "1s", + }, + Accounts: []config.AgmaAnalyticsAccount{ + { + PublisherId: "track-me", + Code: "abc", + SiteAppId: "do-not-track", + }, + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mockedSender := new(MockedSender) + mockedSender.On("Send", mock.Anything).Return(nil) + clockMock := clock.NewMock() + logger, err := newAgmaLogger(tc.config, mockedSender.Send, clockMock) + assert.NoError(t, err) + + go logger.start() + assert.Zero(t, logger.eventCount) + + logger.LogAuctionObject(&mockValidAuctionObject) + logger.LogVideoObject(&mockValidVideoObject) + logger.LogAmpObject(&mockValidAmpObject) + + clockMock.Add(2 * time.Minute) + mockedSender.AssertNumberOfCalls(t, "Send", 0) + assert.Zero(t, logger.eventCount) + }) + } +} + +func TestRaceAllEvents(t *testing.T) { + cfg := config.AgmaAnalytics{ + Enabled: true, + Endpoint: config.AgmaAnalyticsHttpEndpoint{ + Url: "http://localhost:8000/event", + Timeout: "5s", + }, + Buffers: config.AgmaAnalyticsBuffer{ + EventCount: 10000, + BufferSize: "100Mb", + Timeout: "5m", + }, + Accounts: mockValidAccounts, + } + mockedSender := new(MockedSender) + mockedSender.On("Send", mock.Anything).Return(nil) + clockMock := clock.NewMock() + logger, err := newAgmaLogger(cfg, mockedSender.Send, clockMock) + assert.NoError(t, err) + + go logger.start() + + logger.LogAuctionObject(&mockValidAuctionObject) + logger.LogVideoObject(&mockValidVideoObject) + logger.LogAmpObject(&mockValidAmpObject) + clockMock.Add(10 * time.Millisecond) + + logger.mux.RLock() + assert.Equal(t, int64(3), logger.eventCount) + logger.mux.RUnlock() +} + +func TestFlushOnSigterm(t *testing.T) { + cfg := config.AgmaAnalytics{ + Enabled: true, + Endpoint: config.AgmaAnalyticsHttpEndpoint{ + Url: "http://localhost:8000/event", + Timeout: "5s", + }, + Buffers: config.AgmaAnalyticsBuffer{ + EventCount: 10000, + BufferSize: "100Mb", + Timeout: "5m", + }, + Accounts: mockValidAccounts, + } + mockedSender := new(MockedSender) + mockedSender.On("Send", mock.Anything).Return(nil) + clockMock := clock.NewMock() + logger, err := newAgmaLogger(cfg, mockedSender.Send, clockMock) + assert.NoError(t, err) + + done := make(chan struct{}) + go func() { + logger.start() + close(done) + }() + + logger.LogAuctionObject(&mockValidAuctionObject) + logger.LogVideoObject(&mockValidVideoObject) + logger.LogAmpObject(&mockValidAmpObject) + + logger.sigTermCh <- syscall.SIGTERM + <-done + + time.Sleep(100 * time.Millisecond) + + mockedSender.AssertCalled(t, "Send", mock.Anything) +} + +func TestRaceBufferCount(t *testing.T) { + cfg := config.AgmaAnalytics{ + Enabled: true, + Endpoint: config.AgmaAnalyticsHttpEndpoint{ + Url: "http://localhost:8000/event", + Timeout: "5s", + }, + Buffers: config.AgmaAnalyticsBuffer{ + EventCount: 2, + BufferSize: "100Mb", + Timeout: "5m", + }, + Accounts: []config.AgmaAnalyticsAccount{ + { + PublisherId: "track-me", + Code: "abc", + }, + }, + } + mockedSender := new(MockedSender) + mockedSender.On("Send", mock.Anything).Return(nil) + clockMock := clock.NewMock() + logger, err := newAgmaLogger(cfg, mockedSender.Send, clockMock) + assert.NoError(t, err) + + go logger.start() + assert.Zero(t, logger.eventCount) + + // Test EventCount Buffer + logger.LogAuctionObject(&mockValidAuctionObject) + + clockMock.Add(1 * time.Millisecond) + + logger.mux.RLock() + assert.Equal(t, int64(1), logger.eventCount) + logger.mux.RUnlock() + + assert.Equal(t, false, logger.isFull()) + + // add 1 more + logger.LogAuctionObject(&mockValidAuctionObject) + clockMock.Add(1 * time.Millisecond) + + // should trigger send and flash the buffer + mockedSender.AssertCalled(t, "Send", mock.Anything) + + logger.mux.RLock() + assert.Equal(t, int64(0), logger.eventCount) + logger.mux.RUnlock() +} + +func TestBufferSize(t *testing.T) { + cfg := config.AgmaAnalytics{ + Enabled: true, + Endpoint: config.AgmaAnalyticsHttpEndpoint{ + Url: "http://localhost:8000/event", + Timeout: "5s", + }, + Buffers: config.AgmaAnalyticsBuffer{ + EventCount: 1000, + BufferSize: "20Kb", + Timeout: "5m", + }, + Accounts: []config.AgmaAnalyticsAccount{ + { + PublisherId: "track-me", + Code: "abc", + }, + }, + } + mockedSender := new(MockedSender) + mockedSender.On("Send", mock.Anything).Return(nil) + clockMock := clock.NewMock() + logger, err := newAgmaLogger(cfg, mockedSender.Send, clockMock) + assert.NoError(t, err) + + go logger.start() + + for i := 0; i < 50; i++ { + logger.LogAuctionObject(&mockValidAuctionObject) + } + clockMock.Add(10 * time.Millisecond) + mockedSender.AssertCalled(t, "Send", mock.Anything) + mockedSender.AssertNumberOfCalls(t, "Send", 1) +} + +func TestBufferTime(t *testing.T) { + cfg := config.AgmaAnalytics{ + Enabled: true, + Endpoint: config.AgmaAnalyticsHttpEndpoint{ + Url: "http://localhost:8000/event", + Timeout: "5s", + }, + Buffers: config.AgmaAnalyticsBuffer{ + EventCount: 1000, + BufferSize: "100mb", + Timeout: "5m", + }, + Accounts: []config.AgmaAnalyticsAccount{ + { + PublisherId: "track-me", + Code: "abc", + }, + }, + } + mockedSender := new(MockedSender) + mockedSender.On("Send", mock.Anything).Return(nil) + clockMock := clock.NewMock() + logger, err := newAgmaLogger(cfg, mockedSender.Send, clockMock) + assert.NoError(t, err) + + go logger.start() + + for i := 0; i < 5; i++ { + logger.LogAuctionObject(&mockValidAuctionObject) + } + clockMock.Add(10 * time.Minute) + mockedSender.AssertCalled(t, "Send", mock.Anything) + mockedSender.AssertNumberOfCalls(t, "Send", 1) +} + +func TestRaceEnd2End(t *testing.T) { + var mu sync.Mutex + + requestBodyAsString := "" + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Check for reponse + requestBody, err := io.ReadAll(r.Body) + mu.Lock() + requestBodyAsString = string(requestBody) + mu.Unlock() + if err != nil { + http.Error(w, "Error reading request body", 500) + return + } + + w.WriteHeader(http.StatusOK) + })) + cfg := config.AgmaAnalytics{ + Enabled: true, + Endpoint: config.AgmaAnalyticsHttpEndpoint{ + Url: server.URL, + Timeout: "5s", + }, + Buffers: config.AgmaAnalyticsBuffer{ + EventCount: 2, + BufferSize: "100mb", + Timeout: "5m", + }, + Accounts: mockValidAccounts, + } + + clockMock := clock.NewMock() + clockMock.Set(time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC)) + + logger, err := NewModule(&http.Client{}, cfg, clockMock) + assert.NoError(t, err) + + logger.LogAmpObject(&mockValidAmpObject) + logger.LogAmpObject(&mockValidAmpObject) + + time.Sleep(250 * time.Millisecond) + + expected := "[{\"type\":\"amp\",\"id\":\"some-id\",\"code\":\"abcd\",\"site\":{\"id\":\"track-me-site\",\"publisher\":{\"id\":\"track-me\"}},\"device\":{\"ua\":\"ua\"},\"user\":{\"consent\":\"" + agmaConsent + "\"},\"created_at\":\"2023-02-01T00:00:00Z\"},{\"type\":\"amp\",\"id\":\"some-id\",\"code\":\"abcd\",\"site\":{\"id\":\"track-me-site\",\"publisher\":{\"id\":\"track-me\"}},\"device\":{\"ua\":\"ua\"},\"user\":{\"consent\":\"" + agmaConsent + "\"},\"created_at\":\"2023-02-01T00:00:00Z\"}]" + + mu.Lock() + actual := requestBodyAsString + mu.Unlock() + + assert.Equal(t, expected, actual) +} + +func TestShutdownFlush(t *testing.T) { + cfg := config.AgmaAnalytics{ + Enabled: true, + Endpoint: config.AgmaAnalyticsHttpEndpoint{ + Url: "http://localhost:8000/event", + Timeout: "5s", + }, + Buffers: config.AgmaAnalyticsBuffer{ + EventCount: 1000, + BufferSize: "100mb", + Timeout: "5m", + }, + Accounts: []config.AgmaAnalyticsAccount{ + { + PublisherId: "track-me", + Code: "abc", + }, + }, + } + mockedSender := new(MockedSender) + mockedSender.On("Send", mock.Anything).Return(nil) + clockMock := clock.NewMock() + logger, err := newAgmaLogger(cfg, mockedSender.Send, clockMock) + assert.NoError(t, err) + + go logger.start() + logger.LogAuctionObject(&mockValidAuctionObject) + logger.Shutdown() + + time.Sleep(10 * time.Millisecond) + + mockedSender.AssertCalled(t, "Send", mock.Anything) + mockedSender.AssertNumberOfCalls(t, "Send", 1) +} diff --git a/analytics/agma/model.go b/analytics/agma/model.go new file mode 100644 index 00000000000..112ce961a0c --- /dev/null +++ b/analytics/agma/model.go @@ -0,0 +1,50 @@ +package agma + +import ( + "fmt" + "time" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +type EventType string + +const ( + EventTypeAuction EventType = "auction" + EventTypeAmp EventType = "amp" + EventTypeVideo EventType = "video" +) + +type logObject struct { + EventType EventType `json:"type"` + RequestId string `json:"id"` + AccountCode string `json:"code"` + Site *openrtb2.Site `json:"site,omitempty"` + App *openrtb2.App `json:"app,omitempty"` + Device *openrtb2.Device `json:"device,omitempty"` + User *openrtb2.User `json:"user,omitempty"` + CreatedAt time.Time `json:"created_at"` +} + +func serializeAnayltics( + requestwrapper *openrtb_ext.RequestWrapper, + eventType EventType, + code string, + createdAt time.Time, +) ([]byte, error) { + if requestwrapper == nil || requestwrapper.BidRequest == nil { + return nil, fmt.Errorf("requestwrapper or BidRequest object nil") + } + return jsonutil.Marshal(&logObject{ + EventType: eventType, + RequestId: requestwrapper.ID, + AccountCode: code, + Site: requestwrapper.BidRequest.Site, + App: requestwrapper.BidRequest.App, + Device: requestwrapper.BidRequest.Device, + User: requestwrapper.BidRequest.User, + CreatedAt: createdAt, + }) +} diff --git a/analytics/agma/model_test.go b/analytics/agma/model_test.go new file mode 100644 index 00000000000..925856b87b8 --- /dev/null +++ b/analytics/agma/model_test.go @@ -0,0 +1,46 @@ +package agma + +import ( + "testing" + "time" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestCheckForNil(t *testing.T) { + code := "test" + _, err := serializeAnayltics(nil, EventTypeAuction, code, time.Now()) + assert.Error(t, err) +} + +func TestSerializeAuctionObject(t *testing.T) { + data, err := serializeAnayltics(&openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "some-id", + }, + }, EventTypeAuction, "test", time.Now()) + assert.NoError(t, err) + assert.Contains(t, string(data), "\"type\":\"auction\"") +} + +func TestSerializeVideoObject(t *testing.T) { + data, err := serializeAnayltics(&openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "some-id", + }, + }, EventTypeVideo, "test", time.Now()) + assert.NoError(t, err) + assert.Contains(t, string(data), "\"type\":\"video\"") +} + +func TestSerializeAmpObject(t *testing.T) { + data, err := serializeAnayltics(&openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "some-id", + }, + }, EventTypeAmp, "test", time.Now()) + assert.NoError(t, err) + assert.Contains(t, string(data), "\"type\":\"amp\"") +} diff --git a/analytics/agma/sender.go b/analytics/agma/sender.go new file mode 100644 index 00000000000..b47b059536e --- /dev/null +++ b/analytics/agma/sender.go @@ -0,0 +1,84 @@ +package agma + +import ( + "bytes" + "compress/gzip" + "context" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/golang/glog" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/version" +) + +func compressToGZIP(requestBody []byte) ([]byte, error) { + var b bytes.Buffer + w := gzip.NewWriter(&b) + _, err := w.Write([]byte(requestBody)) + if err != nil { + _ = w.Close() + return nil, err + } + err = w.Close() + if err != nil { + return nil, err + } + return b.Bytes(), nil +} + +func createHttpSender(httpClient *http.Client, endpoint config.AgmaAnalyticsHttpEndpoint) (httpSender, error) { + _, err := url.Parse(endpoint.Url) + if err != nil { + return nil, err + } + + httpTimeout, err := time.ParseDuration(endpoint.Timeout) + if err != nil { + return nil, err + } + + return func(payload []byte) error { + ctx, cancel := context.WithTimeout(context.Background(), httpTimeout) + defer cancel() + + var requestBody []byte + var err error + + if endpoint.Gzip { + requestBody, err = compressToGZIP(payload) + if err != nil { + glog.Errorf("[agmaAnalytics] Compressing request failed %v", err) + return err + } + } else { + requestBody = payload + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.Url, bytes.NewBuffer(requestBody)) + if err != nil { + glog.Errorf("[agmaAnalytics] Creating request failed %v", err) + return err + } + + req.Header.Set("X-Prebid", version.BuildXPrebidHeader(version.Ver)) + req.Header.Set("Content-Type", "application/json") + if endpoint.Gzip { + req.Header.Set("Content-Encoding", "gzip") + } + + resp, err := httpClient.Do(req) + if err != nil { + glog.Errorf("[agmaAnalytics] Sending request failed %v", err) + return err + } + + if resp.StatusCode != http.StatusOK { + glog.Errorf("[agmaAnalytics] Wrong code received %d instead of %d", resp.StatusCode, http.StatusOK) + return fmt.Errorf("wrong code received %d instead of %d", resp.StatusCode, http.StatusOK) + } + return nil + }, nil +} diff --git a/analytics/agma/sender_test.go b/analytics/agma/sender_test.go new file mode 100644 index 00000000000..1d23e6b6a90 --- /dev/null +++ b/analytics/agma/sender_test.go @@ -0,0 +1,133 @@ +package agma + +import ( + "compress/gzip" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/prebid/prebid-server/v3/config" + "github.com/stretchr/testify/assert" +) + +func TestCreateHttpSender(t *testing.T) { + testCases := []struct { + name string + endpoint config.AgmaAnalyticsHttpEndpoint + wantHeaders http.Header + wantErr bool + }{ + { + name: "Test with invalid/empty URL", + endpoint: config.AgmaAnalyticsHttpEndpoint{ + Url: "%%2815197306101420000%29", + Timeout: "1s", + Gzip: false, + }, + wantErr: true, + }, + { + name: "Test with timeout", + endpoint: config.AgmaAnalyticsHttpEndpoint{ + Url: "http://localhost:8080", + Timeout: "2x", // Very short timeout + Gzip: false, + }, + wantErr: true, + }, + { + name: "Test with Gzip true", + endpoint: config.AgmaAnalyticsHttpEndpoint{ + Url: "http://localhost:8080", + Timeout: "1s", + Gzip: true, + }, + wantHeaders: http.Header{ + "Content-Encoding": []string{"gzip"}, + "Content-Type": []string{"application/json"}, + }, + wantErr: false, + }, + { + name: "Test with Gzip false", + endpoint: config.AgmaAnalyticsHttpEndpoint{ + Url: "http://localhost:8080", + Timeout: "1s", + Gzip: false, + }, + wantHeaders: http.Header{ + "Content-Type": []string{"application/json"}, + }, + wantErr: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + testBody := []byte("[{ \"type\": \"test\" }]") + // Create a test server + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Check the headers + for name, wantValues := range tc.wantHeaders { + assert.Equal(t, wantValues, r.Header[name], "Expected header '%s' to be '%v', got '%v'", name, wantValues, r.Header[name]) + } + defer r.Body.Close() + var reader io.ReadCloser + var err error + if tc.endpoint.Gzip { + reader, err = gzip.NewReader(r.Body) + assert.NoError(t, err) + defer reader.Close() + } else { + reader = r.Body + } + + decompressedData, err := io.ReadAll(reader) + assert.NoError(t, err) + + assert.Equal(t, string(testBody), string(decompressedData)) + + w.WriteHeader(http.StatusOK) + })) + defer ts.Close() + + // Update the URL of the endpoint to the URL of the test server + if !tc.wantErr { + tc.endpoint.Url = ts.URL + } + + // Create a test client + client := &http.Client{} + + // Test the createHttpSender function + sender, err := createHttpSender(client, tc.endpoint) + if tc.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + + // Test the returned HttpSender function + err = sender([]byte(testBody)) + assert.NoError(t, err) + }) + } +} + +func TestSenderErrorReponse(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + })) + defer ts.Close() + + client := &http.Client{} + sender, err := createHttpSender(client, config.AgmaAnalyticsHttpEndpoint{ + Url: ts.URL, + Timeout: "1s", + Gzip: false, + }) + testBody := []byte("[{ \"type\": \"test\" }]") + err = sender([]byte(testBody)) + assert.Error(t, err) +} diff --git a/analytics/build/build.go b/analytics/build/build.go index 6fb48705981..26edaa4f53b 100644 --- a/analytics/build/build.go +++ b/analytics/build/build.go @@ -1,14 +1,19 @@ package build import ( + "encoding/json" + "github.com/benbjohnson/clock" "github.com/golang/glog" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/analytics/clients" - "github.com/prebid/prebid-server/analytics/filesystem" - "github.com/prebid/prebid-server/analytics/pubstack" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/analytics/agma" + "github.com/prebid/prebid-server/v3/analytics/clients" + "github.com/prebid/prebid-server/v3/analytics/filesystem" + "github.com/prebid/prebid-server/v3/analytics/pubstack" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/ortb" + "github.com/prebid/prebid-server/v3/privacy" ) // Modules that need to be logged to need to be initialized here @@ -38,6 +43,19 @@ func New(analytics *config.Analytics) analytics.Runner { glog.Errorf("Could not initialize PubstackModule: %v", err) } } + + if analytics.Agma.Enabled { + agmaModule, err := agma.NewModule( + clients.GetDefaultHttpInstance(), + analytics.Agma, + clock.New()) + if err == nil { + modules["agma"] = agmaModule + } else { + glog.Errorf("Could not initialize Agma Anayltics: %v", err) + } + } + return modules } @@ -46,19 +64,32 @@ type enabledAnalytics map[string]analytics.Module func (ea enabledAnalytics) LogAuctionObject(ao *analytics.AuctionObject, ac privacy.ActivityControl) { for name, module := range ea { - component := privacy.Component{Type: privacy.ComponentTypeAnalytics, Name: name} - if ac.Allow(privacy.ActivityReportAnalytics, component, privacy.ActivityRequest{}) { + if isAllowed, cloneBidderReq := evaluateActivities(ao.RequestWrapper, ac, name); isAllowed { + if cloneBidderReq != nil { + ao.RequestWrapper = cloneBidderReq + } + cloneReq := updateReqWrapperForAnalytics(ao.RequestWrapper, name, cloneBidderReq != nil) module.LogAuctionObject(ao) + if cloneReq != nil { + ao.RequestWrapper = cloneReq + } } } } func (ea enabledAnalytics) LogVideoObject(vo *analytics.VideoObject, ac privacy.ActivityControl) { for name, module := range ea { - component := privacy.Component{Type: privacy.ComponentTypeAnalytics, Name: name} - if ac.Allow(privacy.ActivityReportAnalytics, component, privacy.ActivityRequest{}) { + if isAllowed, cloneBidderReq := evaluateActivities(vo.RequestWrapper, ac, name); isAllowed { + if cloneBidderReq != nil { + vo.RequestWrapper = cloneBidderReq + } + cloneReq := updateReqWrapperForAnalytics(vo.RequestWrapper, name, cloneBidderReq != nil) module.LogVideoObject(vo) + if cloneReq != nil { + vo.RequestWrapper = cloneReq + } } + } } @@ -76,9 +107,15 @@ func (ea enabledAnalytics) LogSetUIDObject(so *analytics.SetUIDObject) { func (ea enabledAnalytics) LogAmpObject(ao *analytics.AmpObject, ac privacy.ActivityControl) { for name, module := range ea { - component := privacy.Component{Type: privacy.ComponentTypeAnalytics, Name: name} - if ac.Allow(privacy.ActivityReportAnalytics, component, privacy.ActivityRequest{}) { + if isAllowed, cloneBidderReq := evaluateActivities(ao.RequestWrapper, ac, name); isAllowed { + if cloneBidderReq != nil { + ao.RequestWrapper = cloneBidderReq + } + cloneReq := updateReqWrapperForAnalytics(ao.RequestWrapper, name, cloneBidderReq != nil) module.LogAmpObject(ao) + if cloneReq != nil { + ao.RequestWrapper = cloneReq + } } } } @@ -91,3 +128,85 @@ func (ea enabledAnalytics) LogNotificationEventObject(ne *analytics.Notification } } } + +// Shutdown - correctly shutdown all analytics modules and wait for them to finish +func (ea enabledAnalytics) Shutdown() { + for _, module := range ea { + module.Shutdown() + } +} + +func evaluateActivities(rw *openrtb_ext.RequestWrapper, ac privacy.ActivityControl, componentName string) (bool, *openrtb_ext.RequestWrapper) { + // returned nil request wrapper means that request wrapper was not modified by activities and doesn't have to be changed in analytics object + // it is needed in order to use one function for all analytics objects with RequestWrapper + component := privacy.Component{Type: privacy.ComponentTypeAnalytics, Name: componentName} + if !ac.Allow(privacy.ActivityReportAnalytics, component, privacy.ActivityRequest{}) { + return false, nil + } + blockUserFPD := !ac.Allow(privacy.ActivityTransmitUserFPD, component, privacy.ActivityRequest{}) + blockPreciseGeo := !ac.Allow(privacy.ActivityTransmitPreciseGeo, component, privacy.ActivityRequest{}) + + if !blockUserFPD && !blockPreciseGeo { + return true, nil + } + + cloneReq := &openrtb_ext.RequestWrapper{ + BidRequest: ortb.CloneBidRequestPartial(rw.BidRequest), + } + + if blockUserFPD { + privacy.ScrubUserFPD(cloneReq) + } + if blockPreciseGeo { + ipConf := privacy.IPConf{IPV6: ac.IPv6Config, IPV4: ac.IPv4Config} + privacy.ScrubGeoAndDeviceIP(cloneReq, ipConf) + } + + cloneReq.RebuildRequest() + return true, cloneReq +} + +func updateReqWrapperForAnalytics(rw *openrtb_ext.RequestWrapper, adapterName string, isCloned bool) *openrtb_ext.RequestWrapper { + if rw == nil { + return nil + } + reqExt, _ := rw.GetRequestExt() + reqExtPrebid := reqExt.GetPrebid() + if reqExtPrebid == nil { + return nil + } + + var cloneReq *openrtb_ext.RequestWrapper + if !isCloned { + cloneReq = &openrtb_ext.RequestWrapper{BidRequest: ortb.CloneBidRequestPartial(rw.BidRequest)} + } else { + cloneReq = nil + } + + if len(reqExtPrebid.Analytics) == 0 { + return cloneReq + } + + // Remove the entire analytics object if the adapter module is not present + if _, ok := reqExtPrebid.Analytics[adapterName]; !ok { + reqExtPrebid.Analytics = nil + } else { + reqExtPrebid.Analytics = updatePrebidAnalyticsMap(reqExtPrebid.Analytics, adapterName) + } + reqExt.SetPrebid(reqExtPrebid) + rw.RebuildRequest() + + if cloneReq != nil { + cloneReq.RebuildRequest() + } + + return cloneReq +} + +func updatePrebidAnalyticsMap(extPrebidAnalytics map[string]json.RawMessage, adapterName string) map[string]json.RawMessage { + newMap := make(map[string]json.RawMessage) + if val, ok := extPrebidAnalytics[adapterName]; ok { + newMap[adapterName] = val + } + return newMap +} diff --git a/analytics/build/build_test.go b/analytics/build/build_test.go index dbd129a7afd..ae16e0f749b 100644 --- a/analytics/build/build_test.go +++ b/analytics/build/build_test.go @@ -1,15 +1,18 @@ package build import ( - "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/iputil" + "net/http" "os" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/privacy" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -19,9 +22,10 @@ func TestSampleModule(t *testing.T) { var count int am := initAnalytics(&count) am.LogAuctionObject(&analytics.AuctionObject{ - Status: http.StatusOK, - Errors: nil, - Response: &openrtb2.BidResponse{}, + Status: http.StatusOK, + RequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: getDefaultBidRequest()}, + Errors: nil, + Response: &openrtb2.BidResponse{}, }, privacy.ActivityControl{}) if count != 1 { t.Errorf("PBSAnalyticsModule failed at LogAuctionObject") @@ -43,12 +47,12 @@ func TestSampleModule(t *testing.T) { t.Errorf("PBSAnalyticsModule failed at LogCookieSyncObject") } - am.LogAmpObject(&analytics.AmpObject{}, privacy.ActivityControl{}) + am.LogAmpObject(&analytics.AmpObject{RequestWrapper: &openrtb_ext.RequestWrapper{}}, privacy.ActivityControl{}) if count != 4 { t.Errorf("PBSAnalyticsModule failed at LogAmpObject") } - am.LogVideoObject(&analytics.VideoObject{}, privacy.ActivityControl{}) + am.LogVideoObject(&analytics.VideoObject{RequestWrapper: &openrtb_ext.RequestWrapper{}}, privacy.ActivityControl{}) if count != 5 { t.Errorf("PBSAnalyticsModule failed at LogVideoObject") } @@ -75,6 +79,8 @@ func (m *sampleModule) LogAmpObject(ao *analytics.AmpObject) { *m.count++ } func (m *sampleModule) LogNotificationEventObject(ne *analytics.NotificationEvent) { *m.count++ } +func (m *sampleModule) Shutdown() { *m.count++ } + func initAnalytics(count *int) analytics.Runner { modules := make(enabledAnalytics, 0) modules["sampleModule"] = &sampleModule{count} @@ -88,6 +94,19 @@ func TestNewPBSAnalytics(t *testing.T) { assert.Equal(t, len(instance), 0) } +func TestPBSAnalyticsShutdown(t *testing.T) { + countA := 0 + countB := 0 + modules := make(enabledAnalytics, 0) + modules["sampleModuleA"] = &sampleModule{count: &countA} + modules["sampleModuleB"] = &sampleModule{count: &countB} + + modules.Shutdown() + + assert.Equal(t, 1, countA, "sampleModuleA should have been shutdown") + assert.Equal(t, 1, countB, "sampleModuleB should have been shutdown") +} + func TestNewPBSAnalytics_FileLogger(t *testing.T) { if _, err := os.Stat(TEST_DIR); os.IsNotExist(err) { if err = os.MkdirAll(TEST_DIR, 0755); err != nil { @@ -112,7 +131,6 @@ func TestNewPBSAnalytics_FileLogger(t *testing.T) { } func TestNewPBSAnalytics_Pubstack(t *testing.T) { - pbsAnalyticsWithoutError := New(&config.Analytics{ Pubstack: config.Pubstack{ Enabled: true, @@ -139,16 +157,51 @@ func TestNewPBSAnalytics_Pubstack(t *testing.T) { assert.Equal(t, len(instanceWithError), 0) } +func TestNewModuleHttp(t *testing.T) { + agmaAnalyticsWithoutError := New(&config.Analytics{ + Agma: config.AgmaAnalytics{ + Enabled: true, + Endpoint: config.AgmaAnalyticsHttpEndpoint{ + Url: "http://localhost:8080", + Timeout: "1s", + }, + Buffers: config.AgmaAnalyticsBuffer{ + BufferSize: "100KB", + EventCount: 50, + Timeout: "30s", + }, + Accounts: []config.AgmaAnalyticsAccount{ + { + PublisherId: "123", + Code: "abc", + }, + }, + }, + }) + instanceWithoutError := agmaAnalyticsWithoutError.(enabledAnalytics) + + assert.Equal(t, len(instanceWithoutError), 1) + + agmaAnalyticsWithError := New(&config.Analytics{ + Agma: config.AgmaAnalytics{ + Enabled: true, + }, + }) + instanceWithError := agmaAnalyticsWithError.(enabledAnalytics) + assert.Equal(t, len(instanceWithError), 0) +} + func TestSampleModuleActivitiesAllowed(t *testing.T) { var count int am := initAnalytics(&count) - acAllowed := privacy.NewActivityControl(getDefaultActivityConfig("sampleModule", true)) + acAllowed := privacy.NewActivityControl(getActivityConfig("sampleModule", true, true, true)) ao := &analytics.AuctionObject{ - Status: http.StatusOK, - Errors: nil, - Response: &openrtb2.BidResponse{}, + Status: http.StatusOK, + RequestWrapper: &openrtb_ext.RequestWrapper{}, + Errors: nil, + Response: &openrtb2.BidResponse{}, } am.LogAuctionObject(ao, acAllowed) @@ -156,12 +209,47 @@ func TestSampleModuleActivitiesAllowed(t *testing.T) { t.Errorf("PBSAnalyticsModule failed at LogAuctionObject") } - am.LogAmpObject(&analytics.AmpObject{}, acAllowed) + am.LogAmpObject(&analytics.AmpObject{RequestWrapper: &openrtb_ext.RequestWrapper{}}, acAllowed) if count != 2 { t.Errorf("PBSAnalyticsModule failed at LogAmpObject") } - am.LogVideoObject(&analytics.VideoObject{}, acAllowed) + am.LogVideoObject(&analytics.VideoObject{RequestWrapper: &openrtb_ext.RequestWrapper{}}, acAllowed) + if count != 3 { + t.Errorf("PBSAnalyticsModule failed at LogVideoObject") + } + + am.LogNotificationEventObject(&analytics.NotificationEvent{}, acAllowed) + if count != 4 { + t.Errorf("PBSAnalyticsModule failed at LogNotificationEventObject") + } +} + +func TestSampleModuleActivitiesAllowedAndDenied(t *testing.T) { + var count int + am := initAnalytics(&count) + + acAllowed := privacy.NewActivityControl(getActivityConfig("sampleModule", true, false, true)) + + rw := &openrtb_ext.RequestWrapper{BidRequest: getDefaultBidRequest()} + ao := &analytics.AuctionObject{ + RequestWrapper: rw, + Status: http.StatusOK, + Errors: nil, + Response: &openrtb2.BidResponse{}, + } + + am.LogAuctionObject(ao, acAllowed) + if count != 1 { + t.Errorf("PBSAnalyticsModule failed at LogAuctionObject") + } + + am.LogAmpObject(&analytics.AmpObject{RequestWrapper: rw}, acAllowed) + if count != 2 { + t.Errorf("PBSAnalyticsModule failed at LogAmpObject") + } + + am.LogVideoObject(&analytics.VideoObject{RequestWrapper: rw}, acAllowed) if count != 3 { t.Errorf("PBSAnalyticsModule failed at LogVideoObject") } @@ -176,7 +264,7 @@ func TestSampleModuleActivitiesDenied(t *testing.T) { var count int am := initAnalytics(&count) - acDenied := privacy.NewActivityControl(getDefaultActivityConfig("sampleModule", false)) + acDenied := privacy.NewActivityControl(getActivityConfig("sampleModule", false, true, true)) ao := &analytics.AuctionObject{ Status: http.StatusOK, @@ -205,14 +293,90 @@ func TestSampleModuleActivitiesDenied(t *testing.T) { } } -func getDefaultActivityConfig(componentName string, allow bool) *config.AccountPrivacy { +func TestEvaluateActivities(t *testing.T) { + testCases := []struct { + description string + givenActivityControl privacy.ActivityControl + expectedRequest *openrtb_ext.RequestWrapper + expectedAllowActivities bool + }{ + { + description: "all blocked", + givenActivityControl: privacy.NewActivityControl(getActivityConfig("sampleModule", false, false, false)), + expectedRequest: nil, + expectedAllowActivities: false, + }, + { + description: "all allowed", + givenActivityControl: privacy.NewActivityControl(getActivityConfig("sampleModule", true, true, true)), + expectedRequest: nil, + expectedAllowActivities: true, + }, + + { + description: "ActivityTransmitUserFPD and ActivityTransmitPreciseGeo disabled", + givenActivityControl: privacy.NewActivityControl(getActivityConfig("sampleModule", true, false, false)), + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ID: "test_request", User: &openrtb2.User{ID: ""}, Device: &openrtb2.Device{IFA: "", IP: "127.0.0.0"}}, + }, + expectedAllowActivities: true, + }, + { + description: "ActivityTransmitUserFPD enabled, ActivityTransmitPreciseGeo disabled", + givenActivityControl: privacy.NewActivityControl(getActivityConfig("sampleModule", true, true, false)), + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ID: "test_request", User: &openrtb2.User{ID: "user-id"}, Device: &openrtb2.Device{IFA: "device-ifa", IP: "127.0.0.0"}}, + }, + expectedAllowActivities: true, + }, + } + + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + rw := &openrtb_ext.RequestWrapper{BidRequest: getDefaultBidRequest()} + resActivityAllowed, resRequest := evaluateActivities(rw, test.givenActivityControl, "sampleModule") + assert.Equal(t, test.expectedAllowActivities, resActivityAllowed) + if test.expectedRequest != nil { + assert.Equal(t, test.expectedRequest.User.ID, resRequest.User.ID) + assert.Equal(t, test.expectedRequest.Device.IFA, resRequest.Device.IFA) + assert.Equal(t, test.expectedRequest.Device.IP, resRequest.Device.IP) + } else { + assert.Nil(t, resRequest) + } + + }) + } + +} + +func getDefaultBidRequest() *openrtb2.BidRequest { + return &openrtb2.BidRequest{ + ID: "test_request", + User: &openrtb2.User{ID: "user-id"}, + Device: &openrtb2.Device{IFA: "device-ifa", IP: "127.0.0.1"}, + } +} + +func getActivityConfig(componentName string, allowReportAnalytics, allowTransmitUserFPD, allowTransmitPreciseGeo bool) *config.AccountPrivacy { return &config.AccountPrivacy{ AllowActivities: &config.AllowActivities{ ReportAnalytics: config.Activity{ Default: ptrutil.ToPtr(true), Rules: []config.ActivityRule{ { - Allow: allow, + Allow: allowReportAnalytics, + Condition: config.ActivityCondition{ + ComponentName: []string{componentName}, + ComponentType: []string{"analytics"}, + }, + }, + }, + }, + TransmitUserFPD: config.Activity{ + Default: ptrutil.ToPtr(true), + Rules: []config.ActivityRule{ + { + Allow: allowTransmitUserFPD, Condition: config.ActivityCondition{ ComponentName: []string{componentName}, ComponentType: []string{"analytics"}, @@ -220,6 +384,247 @@ func getDefaultActivityConfig(componentName string, allow bool) *config.AccountP }, }, }, + TransmitPreciseGeo: config.Activity{ + Default: ptrutil.ToPtr(true), + Rules: []config.ActivityRule{ + { + Allow: allowTransmitPreciseGeo, + Condition: config.ActivityCondition{ + ComponentName: []string{componentName}, + ComponentType: []string{"analytics"}, + }, + }, + }, + }, + }, + IPv4Config: config.IPv4{ + AnonKeepBits: iputil.IPv4DefaultMaskingBitSize, + }, + IPv6Config: config.IPv6{ + AnonKeepBits: iputil.IPv6DefaultMaskingBitSize, }, } } + +type mockAnalytics struct { + lastLoggedAuctionBidRequest *openrtb2.BidRequest + lastLoggedAmpBidRequest *openrtb2.BidRequest + lastLoggedVideoBidRequest *openrtb2.BidRequest +} + +func (m *mockAnalytics) LogAuctionObject(ao *analytics.AuctionObject) { + m.lastLoggedAuctionBidRequest = ao.RequestWrapper.BidRequest +} + +func (m *mockAnalytics) LogAmpObject(ao *analytics.AmpObject) { + m.lastLoggedAmpBidRequest = ao.RequestWrapper.BidRequest +} + +func (m *mockAnalytics) LogVideoObject(vo *analytics.VideoObject) { + m.lastLoggedVideoBidRequest = vo.RequestWrapper.BidRequest +} + +func (m *mockAnalytics) LogCookieSyncObject(ao *analytics.CookieSyncObject) {} + +func (m *mockAnalytics) LogSetUIDObject(ao *analytics.SetUIDObject) {} + +func (m *mockAnalytics) LogNotificationEventObject(ao *analytics.NotificationEvent) {} + +func (m *mockAnalytics) Shutdown() {} + +func TestLogObject(t *testing.T) { + tests := []struct { + description string + givenRequestWrapper *openrtb_ext.RequestWrapper + givenEnabledAnalytics enabledAnalytics + givenActivityControl bool + givenAuctionObject *analytics.AuctionObject + givenAmpObject *analytics.AmpObject + givenVideoObject *analytics.VideoObject + expectedBidRequest1 *openrtb2.BidRequest + expectedBidRequest2 *openrtb2.BidRequest + }{ + { + description: "Multiple analytics modules, clone from evaluate activities, should expect both to have their information to be logged only -- auction", + givenEnabledAnalytics: enabledAnalytics{"adapter1": &mockAnalytics{}, "adapter2": &mockAnalytics{}}, + givenActivityControl: true, + givenAuctionObject: &analytics.AuctionObject{ + Status: http.StatusOK, + Errors: nil, + Response: &openrtb2.BidResponse{}, + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "test_request", + Ext: []byte(`{"prebid":{"analytics":{"adapter1":{"client-analytics":true},"adapter2":{"client-analytics":false}}}}`)}, + }, + }, + expectedBidRequest1: &openrtb2.BidRequest{ + ID: "test_request", + Ext: []byte(`{"prebid":{"analytics":{"adapter1":{"client-analytics":true}}}}`)}, + expectedBidRequest2: &openrtb2.BidRequest{ + ID: "test_request", + Ext: []byte(`{"prebid":{"analytics":{"adapter2":{"client-analytics":false}}}}`)}, + }, + { + description: "Multiple analytics modules, no clone from evaluate activities, should expect both to have their information to be logged only -- amp", + givenEnabledAnalytics: enabledAnalytics{"adapter1": &mockAnalytics{}, "adapter2": &mockAnalytics{}}, + givenActivityControl: false, + givenAmpObject: &analytics.AmpObject{ + Status: http.StatusOK, + Errors: nil, + AuctionResponse: &openrtb2.BidResponse{}, + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "test_request", + Ext: []byte(`{"prebid":{"analytics":{"adapter1":{"client-analytics":true},"adapter2":{"client-analytics":false}}}}`)}, + }, + }, + expectedBidRequest1: &openrtb2.BidRequest{ + ID: "test_request", + Ext: []byte(`{"prebid":{"analytics":{"adapter1":{"client-analytics":true}}}}`)}, + expectedBidRequest2: &openrtb2.BidRequest{ + ID: "test_request", + Ext: []byte(`{"prebid":{"analytics":{"adapter2":{"client-analytics":false}}}}`)}, + }, + { + description: "Single analytics module, clone from evaluate activities, should expect both to have their information to be logged only -- amp", + givenEnabledAnalytics: enabledAnalytics{"adapter1": &mockAnalytics{}}, + givenActivityControl: true, + givenAuctionObject: &analytics.AuctionObject{ + Status: http.StatusOK, + Errors: nil, + Response: &openrtb2.BidResponse{}, + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "test_request", + Ext: []byte(`{"prebid":{"analytics":{"adapter1":{"client-analytics":true},"adapter2":{"client-analytics":false}}}}`)}, + }, + }, + expectedBidRequest1: &openrtb2.BidRequest{ + ID: "test_request", + Ext: []byte(`{"prebid":{"analytics":{"adapter1":{"client-analytics":true}}}}`)}, + }, + { + description: "Single analytics module, adapter name not found, expect entire analytics object to be nil -- video", + givenEnabledAnalytics: enabledAnalytics{"unknownAdapter": &mockAnalytics{}}, + givenActivityControl: true, + givenVideoObject: &analytics.VideoObject{ + Status: http.StatusOK, + Errors: nil, + Response: &openrtb2.BidResponse{}, + RequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "test_request", + Ext: []byte(`{"prebid":{"analytics":{"adapter1":{"client-analytics":true},"adapter2":{"client-analytics":false}}}}`)}, + }, + }, + expectedBidRequest1: &openrtb2.BidRequest{ + ID: "test_request", + Ext: nil, + }, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + ac := privacy.NewActivityControl(getActivityConfig("sampleModule", test.givenActivityControl, test.givenActivityControl, test.givenActivityControl)) + + var loggedBidReq1, loggedBidReq2 *openrtb2.BidRequest + switch { + case test.givenAuctionObject != nil: + test.givenEnabledAnalytics.LogAuctionObject(test.givenAuctionObject, ac) + loggedBidReq1 = test.givenEnabledAnalytics["adapter1"].(*mockAnalytics).lastLoggedAuctionBidRequest + if len(test.givenEnabledAnalytics) == 2 { + loggedBidReq2 = test.givenEnabledAnalytics["adapter2"].(*mockAnalytics).lastLoggedAuctionBidRequest + } + case test.givenAmpObject != nil: + test.givenEnabledAnalytics.LogAmpObject(test.givenAmpObject, ac) + loggedBidReq1 = test.givenEnabledAnalytics["adapter1"].(*mockAnalytics).lastLoggedAmpBidRequest + if len(test.givenEnabledAnalytics) == 2 { + loggedBidReq2 = test.givenEnabledAnalytics["adapter2"].(*mockAnalytics).lastLoggedAmpBidRequest + } + case test.givenVideoObject != nil: + test.givenEnabledAnalytics.LogVideoObject(test.givenVideoObject, ac) + loggedBidReq1 = test.givenEnabledAnalytics["unknownAdapter"].(*mockAnalytics).lastLoggedVideoBidRequest + } + + assert.Equal(t, test.expectedBidRequest1, loggedBidReq1) + if test.expectedBidRequest2 != nil { + assert.Equal(t, test.expectedBidRequest2, loggedBidReq2) + } + }) + } +} + +func TestUpdateReqWrapperForAnalytics(t *testing.T) { + tests := []struct { + description string + givenReqWrapper *openrtb_ext.RequestWrapper + givenAdapterName string + givenIsCloned bool + expectedUpdatedBidRequest *openrtb2.BidRequest + expectedCloneRequest *openrtb_ext.RequestWrapper + }{ + { + description: "Adapter1 so Adapter2 info should be removed from ext.prebid.analytics", + givenReqWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: []byte(`{"prebid":{"analytics":{"adapter1":{"client-analytics":true},"adapter2":{"client-analytics":false}}}}`)}, + }, + givenAdapterName: "adapter1", + givenIsCloned: false, + expectedUpdatedBidRequest: &openrtb2.BidRequest{ + Ext: []byte(`{"prebid":{"analytics":{"adapter1":{"client-analytics":true}}}}`), + }, + expectedCloneRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: []byte(`{"prebid":{"analytics":{"adapter1":{"client-analytics":true},"adapter2":{"client-analytics":false}}}}`)}, + }, + }, + { + description: "Adapter2 so Adapter1 info should be removed from ext.prebid.analytics", + givenReqWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: []byte(`{"prebid":{"analytics":{"adapter1":{"client-analytics":true},"adapter2":{"client-analytics":false}}}}`)}, + }, + givenAdapterName: "adapter2", + givenIsCloned: true, + expectedUpdatedBidRequest: &openrtb2.BidRequest{ + Ext: []byte(`{"prebid":{"analytics":{"adapter2":{"client-analytics":false}}}}`), + }, + expectedCloneRequest: nil, + }, + { + description: "Given adapter not found in ext.prebid.analytics so remove entire object", + givenReqWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: []byte(`{"prebid":{"analytics":{"adapter1":{"client-analytics":true},"adapter2":{"client-analytics":false}}}}`)}, + }, + givenAdapterName: "adapterNotFound", + givenIsCloned: false, + expectedUpdatedBidRequest: &openrtb2.BidRequest{}, + expectedCloneRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: []byte(`{"prebid":{"analytics":{"adapter1":{"client-analytics":true},"adapter2":{"client-analytics":false}}}}`)}, + }, + }, + { + description: "Given request is nil, check there are no exceptions", + givenReqWrapper: nil, + givenAdapterName: "adapter1", + givenIsCloned: false, + expectedUpdatedBidRequest: nil, + expectedCloneRequest: nil, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + cloneReq := updateReqWrapperForAnalytics(test.givenReqWrapper, test.givenAdapterName, test.givenIsCloned) + if test.givenReqWrapper != nil { + assert.Equal(t, test.expectedUpdatedBidRequest, test.givenReqWrapper.BidRequest) + } + assert.Equal(t, test.expectedCloneRequest, cloneReq) + }) + } +} diff --git a/analytics/core.go b/analytics/core.go index 0279ae83868..25ff46c8247 100644 --- a/analytics/core.go +++ b/analytics/core.go @@ -3,10 +3,10 @@ package analytics import ( "time" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // Module must be implemented by analytics modules to extract the required information and logging @@ -19,6 +19,7 @@ type Module interface { LogSetUIDObject(*SetUIDObject) LogAmpObject(*AmpObject) LogNotificationEventObject(*NotificationEvent) + Shutdown() } // Loggable object of a transaction at /openrtb2/auction endpoint diff --git a/analytics/filesystem/file_module.go b/analytics/filesystem/file_module.go index 24b7be6b599..5555752e461 100644 --- a/analytics/filesystem/file_module.go +++ b/analytics/filesystem/file_module.go @@ -4,10 +4,11 @@ import ( "bytes" "fmt" - "github.com/chasex/glog" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/util/jsonutil" + cglog "github.com/chasex/glog" + "github.com/golang/glog" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type RequestType string @@ -21,9 +22,14 @@ const ( NOTIFICATION_EVENT RequestType = "/event" ) +type Logger interface { + Debug(v ...interface{}) + Flush() +} + // Module that can perform transactional logging type FileLogger struct { - Logger *glog.Logger + Logger Logger } // Writes AuctionObject to file @@ -85,15 +91,22 @@ func (f *FileLogger) LogNotificationEventObject(ne *analytics.NotificationEvent) f.Logger.Flush() } +// Shutdown the logger +func (f *FileLogger) Shutdown() { + // clear all pending buffered data in case there is any + glog.Info("[FileLogger] Shutdown, trying to flush buffer") + f.Logger.Flush() +} + // Method to initialize the analytic module func NewFileLogger(filename string) (analytics.Module, error) { - options := glog.LogOptions{ + options := cglog.LogOptions{ File: filename, - Flag: glog.LstdFlags, - Level: glog.Ldebug, - Mode: glog.R_Day, + Flag: cglog.LstdFlags, + Level: cglog.Ldebug, + Mode: cglog.R_Day, } - if logger, err := glog.New(options); err == nil { + if logger, err := cglog.New(options); err == nil { return &FileLogger{ logger, }, nil diff --git a/analytics/filesystem/file_module_test.go b/analytics/filesystem/file_module_test.go index 9843a8ab108..0e502cfb9e0 100644 --- a/analytics/filesystem/file_module_test.go +++ b/analytics/filesystem/file_module_test.go @@ -6,14 +6,27 @@ import ( "strings" "testing" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/config" + "github.com/stretchr/testify/mock" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" ) const TEST_DIR string = "testFiles" +type MockLogger struct { + mock.Mock +} + +func (ml *MockLogger) Debug(v ...interface{}) { + ml.Called(v) +} + +func (ml *MockLogger) Flush() { + ml.Called() +} + func TestAmpObject_ToJson(t *testing.T) { ao := &analytics.AmpObject{ Status: http.StatusOK, @@ -97,3 +110,15 @@ func TestFileLogger_LogObjects(t *testing.T) { t.Fatalf("Couldn't initialize file logger: %v", err) } } + +func TestFileLoggerShutdown(t *testing.T) { + mockLogger := &MockLogger{} + fl := &FileLogger{ + Logger: mockLogger, + } + mockLogger.On("Flush").Return(nil) + + fl.Shutdown() + + mockLogger.AssertNumberOfCalls(t, "Flush", 1) +} diff --git a/analytics/filesystem/model.go b/analytics/filesystem/model.go index 9fc7a6e19a2..aed111dd10a 100644 --- a/analytics/filesystem/model.go +++ b/analytics/filesystem/model.go @@ -3,11 +3,11 @@ package filesystem import ( "time" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) type logAuction struct { diff --git a/analytics/pubstack/configupdate.go b/analytics/pubstack/configupdate.go index 622161a04f2..166e69554d6 100644 --- a/analytics/pubstack/configupdate.go +++ b/analytics/pubstack/configupdate.go @@ -6,7 +6,7 @@ import ( "net/url" "time" - "github.com/prebid/prebid-server/util/task" + "github.com/prebid/prebid-server/v3/util/task" ) // ConfigUpdateTask publishes configurations until the stop channel is signaled. diff --git a/analytics/pubstack/eventchannel/sender.go b/analytics/pubstack/eventchannel/sender.go index 951de4d414e..fe068b1555f 100644 --- a/analytics/pubstack/eventchannel/sender.go +++ b/analytics/pubstack/eventchannel/sender.go @@ -3,10 +3,11 @@ package eventchannel import ( "bytes" "fmt" - "github.com/golang/glog" "net/http" "net/url" "path" + + "github.com/golang/glog" ) type Sender = func(payload []byte) error @@ -26,6 +27,7 @@ func NewHttpSender(client *http.Client, endpoint string) Sender { if err != nil { return err } + resp.Body.Close() if resp.StatusCode != http.StatusOK { glog.Errorf("[pubstack] Wrong code received %d instead of %d", resp.StatusCode, http.StatusOK) diff --git a/analytics/pubstack/helpers/json.go b/analytics/pubstack/helpers/json.go index f56a4c6194b..fbdc173219e 100644 --- a/analytics/pubstack/helpers/json.go +++ b/analytics/pubstack/helpers/json.go @@ -3,9 +3,9 @@ package helpers import ( "fmt" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) func JsonifyAuctionObject(ao *analytics.AuctionObject, scope string) ([]byte, error) { diff --git a/analytics/pubstack/helpers/json_test.go b/analytics/pubstack/helpers/json_test.go index 07ead724929..1e6282078c0 100644 --- a/analytics/pubstack/helpers/json_test.go +++ b/analytics/pubstack/helpers/json_test.go @@ -4,8 +4,8 @@ import ( "net/http" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/analytics" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/analytics" "github.com/stretchr/testify/assert" ) diff --git a/analytics/pubstack/helpers/model.go b/analytics/pubstack/helpers/model.go index 91b86d7fc86..7dad2f7dade 100644 --- a/analytics/pubstack/helpers/model.go +++ b/analytics/pubstack/helpers/model.go @@ -3,11 +3,11 @@ package helpers import ( "time" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) type logAuction struct { diff --git a/analytics/pubstack/pubstack_module.go b/analytics/pubstack/pubstack_module.go index f2d8726d356..c5bb02e2eb7 100644 --- a/analytics/pubstack/pubstack_module.go +++ b/analytics/pubstack/pubstack_module.go @@ -12,9 +12,9 @@ import ( "github.com/benbjohnson/clock" "github.com/golang/glog" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/analytics/pubstack/eventchannel" - "github.com/prebid/prebid-server/analytics/pubstack/helpers" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/analytics/pubstack/eventchannel" + "github.com/prebid/prebid-server/v3/analytics/pubstack/helpers" ) type Configuration struct { @@ -200,6 +200,12 @@ func (p *PubstackModule) LogAmpObject(ao *analytics.AmpObject) { p.eventChannels[amp].Push(payload) } +// Shutdown - no op since the analytic module already implements system signal handling +// and trying to close a closed channel will cause panic +func (p *PubstackModule) Shutdown() { + glog.Info("[PubstackModule] Shutdown") +} + func (p *PubstackModule) start(c <-chan *Configuration) { for { select { diff --git a/analytics/pubstack/pubstack_module_test.go b/analytics/pubstack/pubstack_module_test.go index 23e110df9c1..e2b4e118c6c 100644 --- a/analytics/pubstack/pubstack_module_test.go +++ b/analytics/pubstack/pubstack_module_test.go @@ -8,8 +8,8 @@ import ( "time" "github.com/benbjohnson/clock" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -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.NonBidExt{Prebid: openrtb_ext.ExtResponseNonBidPrebid{Bid: openrtb_ext.NonBidObject{}}}, }, }, }, diff --git a/analytics/runner.go b/analytics/runner.go index 08f67561f8a..d10370e4857 100644 --- a/analytics/runner.go +++ b/analytics/runner.go @@ -1,7 +1,7 @@ package analytics import ( - "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/v3/privacy" ) type Runner interface { @@ -11,4 +11,5 @@ type Runner interface { LogSetUIDObject(*SetUIDObject) LogAmpObject(*AmpObject, privacy.ActivityControl) LogNotificationEventObject(*NotificationEvent, privacy.ActivityControl) + Shutdown() } diff --git a/bidadjustment/apply.go b/bidadjustment/apply.go index 4fa3b737b16..864d6bf6fb5 100644 --- a/bidadjustment/apply.go +++ b/bidadjustment/apply.go @@ -3,8 +3,8 @@ package bidadjustment import ( "math" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) const ( @@ -21,7 +21,7 @@ const minBid = 0.1 // Apply gets the highest priority adjustment slice given a map of rules, and applies those adjustments to a bid's price func Apply(rules map[string][]openrtb_ext.Adjustment, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo, bidType string) (float64, string) { - adjustments := []openrtb_ext.Adjustment{} + var adjustments []openrtb_ext.Adjustment if len(rules) > 0 { adjustments = get(rules, bidType, string(bidderName), bidInfo.Bid.DealID) } else { diff --git a/bidadjustment/apply_test.go b/bidadjustment/apply_test.go index c0eb74ab419..1a9bf75a20c 100644 --- a/bidadjustment/apply_test.go +++ b/bidadjustment/apply_test.go @@ -3,9 +3,9 @@ package bidadjustment import ( "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) diff --git a/bidadjustment/build_rules.go b/bidadjustment/build_rules.go index bccb3bc86cf..2d0ac7c4cd8 100644 --- a/bidadjustment/build_rules.go +++ b/bidadjustment/build_rules.go @@ -1,8 +1,8 @@ package bidadjustment import ( - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) const ( @@ -60,13 +60,11 @@ func merge(req *openrtb_ext.RequestWrapper, acct *openrtb_ext.ExtRequestPrebidBi } extPrebid := reqExt.GetPrebid() - if extPrebid == nil && acct == nil { - return nil, nil - } - if extPrebid == nil && acct != nil { + if extPrebid == nil || extPrebid.BidAdjustments == nil { return acct, nil } - if extPrebid != nil && acct == nil { + + if acct == nil { return extPrebid.BidAdjustments, nil } diff --git a/bidadjustment/build_rules_test.go b/bidadjustment/build_rules_test.go index 263a782130e..29864bcdbc2 100644 --- a/bidadjustment/build_rules_test.go +++ b/bidadjustment/build_rules_test.go @@ -3,9 +3,9 @@ package bidadjustment import ( "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -220,7 +220,7 @@ func TestMerge(t *testing.T) { testCases := []struct { name string givenRequestWrapper *openrtb_ext.RequestWrapper - givenAccount *config.Account + acctBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments expectedBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments }{ { @@ -228,26 +228,18 @@ func TestMerge(t *testing.T) { givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, }, - givenAccount: &config.Account{ - BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, - }, - }, + acctBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderB": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}}, }, }, }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, - }, - "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, - }, + "bidderA": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}}, + "bidderB": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}}, }, }, }, @@ -257,23 +249,17 @@ func TestMerge(t *testing.T) { givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"audio":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, }, - givenAccount: &config.Account{ - BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, - }, - }, + acctBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}}, }, }, }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, - }, + "bidderA": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}}, }, }, }, @@ -283,14 +269,10 @@ func TestMerge(t *testing.T) { givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"video-instream":{"bidderA":{"dealId":[{ "adjtype": "static", "value": 3.00, "currency": "USD"}]}}}}}}`)}, }, - givenAccount: &config.Account{ - BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderA": { - "diffDealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, - }, - }, + acctBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": {"diffDealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}}, }, }, }, @@ -310,26 +292,18 @@ func TestMerge(t *testing.T) { givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"native":{"bidderA":{"dealId":[{"adjtype": "cpm", "value": 0.18, "currency": "USD"}]}}}}}}`)}, }, - givenAccount: &config.Account{ - BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - Native: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, - }, - }, + acctBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Native: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderB": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}}, }, }, }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ Native: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 0.18, Currency: "USD"}}, - }, - "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, - }, + "bidderA": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 0.18, Currency: "USD"}}}, + "bidderB": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}}, }, }, }, @@ -339,28 +313,20 @@ func TestMerge(t *testing.T) { givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"video-outstream":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, }, - givenAccount: &config.Account{ - BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, - }, - }, + acctBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderB": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}}, }, }, }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, - }, + "bidderB": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}}, }, VideoOutstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, - }, + "bidderA": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}}, }, }, }, @@ -370,23 +336,17 @@ func TestMerge(t *testing.T) { givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"ext":{"bidder": {}}}`)}, }, - givenAccount: &config.Account{ - BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, - }, - }, + acctBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderB": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}}, }, }, }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, - }, + "bidderB": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}}, }, }, }, @@ -396,28 +356,20 @@ func TestMerge(t *testing.T) { givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"video-instream":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, }, - givenAccount: &config.Account{ - BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ - MediaType: openrtb_ext.MediaType{ - WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, - }, - }, + acctBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderB": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}}, }, }, }, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderB": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}, - }, + "bidderB": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}}}, }, VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, - }, + "bidderA": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}}, }, }, }, @@ -427,7 +379,7 @@ func TestMerge(t *testing.T) { givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"ext":{"bidder": {}}}`)}, }, - givenAccount: &config.Account{}, + acctBidAdjustments: nil, expectedBidAdjustments: nil, }, { @@ -435,13 +387,129 @@ func TestMerge(t *testing.T) { givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, }, - givenAccount: &config.Account{}, + acctBidAdjustments: nil, expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ MediaType: openrtb_ext.MediaType{ Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ - "bidderA": { - "dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}, - }, + "bidderA": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}}, + }, + }, + }, + }, + + { + name: "NilExtPrebid-NilExtPrebidBidAdj_NilAcct", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + acctBidAdjustments: nil, + expectedBidAdjustments: nil, + }, + { + name: "NilExtPrebid-NilExtPrebidBidAdj-Acct", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + acctBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}}, + }, + }, + }, + expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}}, + }, + }, + }, + }, + { + name: "NotNilExtPrebid-NilExtBidAdj-NilAcct", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{}}`)}, + }, + acctBidAdjustments: nil, + expectedBidAdjustments: nil, + }, + { + name: "NotNilExtPrebid_NilExtBidAdj_NotNilAcct", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{}}`)}, + }, + acctBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}}, + }, + }, + }, + expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}}, + }, + }, + }, + }, + { + name: "NotNilExtPrebid-NotNilExtBidAdj-NilAcct", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, + }, + acctBidAdjustments: nil, + expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}}, + }, + }, + }, + }, + { + name: "NotNilExtPrebid-NotNilExtBidAdj-NotNilAcct", + givenRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)}, + }, + acctBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderB": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3}}}, + }, + VideoOutstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderC": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3}}}, + }, + Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderD": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3}}}, + }, + Native: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderE": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3}}}, + }, + WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderF": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3}}}, + }, + }, + }, + expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderA": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}}}, + }, + VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderB": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3}}}, + }, + VideoOutstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderC": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3}}}, + }, + Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderD": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3}}}, + }, + Native: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderE": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3}}}, + }, + WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidderF": {"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3}}}, }, }, }, @@ -450,7 +518,7 @@ func TestMerge(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - mergedBidAdj, err := merge(test.givenRequestWrapper, test.givenAccount.BidAdjustments) + mergedBidAdj, err := merge(test.givenRequestWrapper, test.acctBidAdjustments) assert.NoError(t, err) assert.Equal(t, test.expectedBidAdjustments, mergedBidAdj) }) diff --git a/bidadjustment/validate.go b/bidadjustment/validate.go index c0ae3d4a27b..b5383b4d1b5 100644 --- a/bidadjustment/validate.go +++ b/bidadjustment/validate.go @@ -3,7 +3,7 @@ package bidadjustment import ( "math" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // Validate checks whether all provided bid adjustments are valid or not against the requirements defined in the issue diff --git a/bidadjustment/validate_test.go b/bidadjustment/validate_test.go index a0b4eb436eb..dedcddc9af8 100644 --- a/bidadjustment/validate_test.go +++ b/bidadjustment/validate_test.go @@ -3,7 +3,7 @@ package bidadjustment import ( "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/config/account.go b/config/account.go index 92af6c8a97f..a54da82d212 100644 --- a/config/account.go +++ b/config/account.go @@ -7,8 +7,8 @@ import ( "strings" "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/iputil" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/iputil" ) // ChannelType enumerates the values of integrations Prebid Server can configure for an account @@ -58,17 +58,30 @@ type AccountCCPA struct { } type AccountPriceFloors struct { - Enabled bool `mapstructure:"enabled" json:"enabled"` - EnforceFloorsRate int `mapstructure:"enforce_floors_rate" json:"enforce_floors_rate"` - AdjustForBidAdjustment bool `mapstructure:"adjust_for_bid_adjustment" json:"adjust_for_bid_adjustment"` - EnforceDealFloors bool `mapstructure:"enforce_deal_floors" json:"enforce_deal_floors"` - UseDynamicData bool `mapstructure:"use_dynamic_data" json:"use_dynamic_data"` - MaxRule int `mapstructure:"max_rules" json:"max_rules"` - MaxSchemaDims int `mapstructure:"max_schema_dims" json:"max_schema_dims"` + Enabled bool `mapstructure:"enabled" json:"enabled"` + EnforceFloorsRate int `mapstructure:"enforce_floors_rate" json:"enforce_floors_rate"` + AdjustForBidAdjustment bool `mapstructure:"adjust_for_bid_adjustment" json:"adjust_for_bid_adjustment"` + EnforceDealFloors bool `mapstructure:"enforce_deal_floors" json:"enforce_deal_floors"` + UseDynamicData bool `mapstructure:"use_dynamic_data" json:"use_dynamic_data"` + MaxRule int `mapstructure:"max_rules" json:"max_rules"` + MaxSchemaDims int `mapstructure:"max_schema_dims" json:"max_schema_dims"` + Fetcher AccountFloorFetch `mapstructure:"fetch" json:"fetch"` } -func (pf *AccountPriceFloors) validate(errs []error) []error { +// AccountFloorFetch defines the configuration for dynamic floors fetching. +type AccountFloorFetch struct { + Enabled bool `mapstructure:"enabled" json:"enabled"` + URL string `mapstructure:"url" json:"url"` + Timeout int `mapstructure:"timeout_ms" json:"timeout_ms"` + MaxFileSizeKB int `mapstructure:"max_file_size_kb" json:"max_file_size_kb"` + MaxRules int `mapstructure:"max_rules" json:"max_rules"` + MaxAge int `mapstructure:"max_age_sec" json:"max_age_sec"` + Period int `mapstructure:"period_sec" json:"period_sec"` + MaxSchemaDims int `mapstructure:"max_schema_dims" json:"max_schema_dims"` + AccountID string `mapstructure:"accountID" json:"accountID"` +} +func (pf *AccountPriceFloors) validate(errs []error) []error { if pf.EnforceFloorsRate < 0 || pf.EnforceFloorsRate > 100 { errs = append(errs, fmt.Errorf(`account_defaults.price_floors.enforce_floors_rate should be between 0 and 100`)) } @@ -81,6 +94,34 @@ func (pf *AccountPriceFloors) validate(errs []error) []error { errs = append(errs, fmt.Errorf(`account_defaults.price_floors.max_schema_dims should be between 0 and 20`)) } + if pf.Fetcher.Period > pf.Fetcher.MaxAge { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.period_sec should be less than account_defaults.price_floors.fetch.max_age_sec`)) + } + + if pf.Fetcher.Period < 300 { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.period_sec should not be less than 300 seconds`)) + } + + if pf.Fetcher.MaxAge < 600 { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.max_age_sec should not be less than 600 seconds and greater than maximum integer value`)) + } + + if !(pf.Fetcher.Timeout > 10 && pf.Fetcher.Timeout < 10000) { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.timeout_ms should be between 10 to 10,000 miliseconds`)) + } + + if pf.Fetcher.MaxRules < 0 { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.max_rules should be greater than or equal to 0`)) + } + + if pf.Fetcher.MaxFileSizeKB < 0 { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.max_file_size_kb should be greater than or equal to 0`)) + } + + if !(pf.Fetcher.MaxSchemaDims >= 0 && pf.Fetcher.MaxSchemaDims < 20) { + errs = append(errs, fmt.Errorf(`account_defaults.price_floors.fetch.max_schema_dims should not be less than 0 and greater than 20`)) + } + return errs } @@ -186,7 +227,7 @@ func (a *AccountGDPR) PurposeEnforcingVendors(purpose consentconstants.Purpose) } // PurposeVendorExceptions returns the vendor exception map for a given purpose. -func (a *AccountGDPR) PurposeVendorExceptions(purpose consentconstants.Purpose) (value map[openrtb_ext.BidderName]struct{}, exists bool) { +func (a *AccountGDPR) PurposeVendorExceptions(purpose consentconstants.Purpose) (value map[string]struct{}, exists bool) { c, exists := a.PurposeConfigs[purpose] if exists && c.VendorExceptionMap != nil { @@ -221,8 +262,8 @@ type AccountGDPRPurpose struct { EnforcePurpose *bool `mapstructure:"enforce_purpose" json:"enforce_purpose,omitempty"` EnforceVendors *bool `mapstructure:"enforce_vendors" json:"enforce_vendors,omitempty"` // Array of vendor exceptions that is used to create the hash table VendorExceptionMap so vendor names can be instantly accessed - VendorExceptions []openrtb_ext.BidderName `mapstructure:"vendor_exceptions" json:"vendor_exceptions"` - VendorExceptionMap map[openrtb_ext.BidderName]struct{} + VendorExceptions []string `mapstructure:"vendor_exceptions" json:"vendor_exceptions"` + VendorExceptionMap map[string]struct{} } // AccountGDPRSpecialFeature represents account-specific GDPR special feature configuration @@ -294,8 +335,27 @@ func (m AccountModules) ModuleConfig(id string) (json.RawMessage, error) { type AccountPrivacy struct { AllowActivities *AllowActivities `mapstructure:"allowactivities" json:"allowactivities"` + DSA *AccountDSA `mapstructure:"dsa" json:"dsa"` IPv6Config IPv6 `mapstructure:"ipv6" json:"ipv6"` IPv4Config IPv4 `mapstructure:"ipv4" json:"ipv4"` + PrivacySandbox PrivacySandbox `mapstructure:"privacysandbox" json:"privacysandbox"` +} + +type PrivacySandbox struct { + TopicsDomain string `mapstructure:"topicsdomain"` + CookieDeprecation CookieDeprecation `mapstructure:"cookiedeprecation"` +} + +type CookieDeprecation struct { + Enabled bool `mapstructure:"enabled"` + TTLSec int `mapstructure:"ttl_sec"` +} + +// AccountDSA represents DSA configuration +type AccountDSA struct { + Default string `mapstructure:"default" json:"default"` + DefaultUnpacked *openrtb_ext.ExtRegsDSA + GDPROnly bool `mapstructure:"gdpr_only" json:"gdpr_only"` } type IPv6 struct { diff --git a/config/account_test.go b/config/account_test.go index d0c8507deef..97bb5bd0d3f 100644 --- a/config/account_test.go +++ b/config/account_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -476,48 +476,40 @@ func TestPurposeVendorExceptions(t *testing.T) { tests := []struct { description string givePurposeConfigNil bool - givePurpose1ExceptionMap map[openrtb_ext.BidderName]struct{} - givePurpose2ExceptionMap map[openrtb_ext.BidderName]struct{} + givePurpose1ExceptionMap map[string]struct{} + givePurpose2ExceptionMap map[string]struct{} givePurpose consentconstants.Purpose - wantExceptionMap map[openrtb_ext.BidderName]struct{} - wantExceptionMapSet bool + wantExceptionMap map[string]struct{} }{ { description: "Purpose config is nil", givePurposeConfigNil: true, givePurpose: 1, - // wantExceptionMap: map[openrtb_ext.BidderName]struct{}{}, - wantExceptionMap: nil, - wantExceptionMapSet: false, + wantExceptionMap: nil, }, { - description: "Nil - exception map not defined for purpose", - givePurpose: 1, - // wantExceptionMap: map[openrtb_ext.BidderName]struct{}{}, - wantExceptionMap: nil, - wantExceptionMapSet: false, + description: "Nil - exception map not defined for purpose", + givePurpose: 1, + wantExceptionMap: nil, }, { description: "Empty - exception map empty for purpose", givePurpose: 1, - givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{}, - wantExceptionMap: map[openrtb_ext.BidderName]struct{}{}, - wantExceptionMapSet: true, + givePurpose1ExceptionMap: map[string]struct{}{}, + wantExceptionMap: map[string]struct{}{}, }, { description: "Nonempty - exception map with multiple entries for purpose", givePurpose: 1, - givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, - wantExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, - wantExceptionMapSet: true, + givePurpose1ExceptionMap: map[string]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, + wantExceptionMap: map[string]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, }, { description: "Nonempty - exception map with multiple entries for different purpose", givePurpose: 2, - givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, - givePurpose2ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}}, - wantExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}}, - wantExceptionMapSet: true, + givePurpose1ExceptionMap: map[string]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, + givePurpose2ExceptionMap: map[string]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}}, + wantExceptionMap: map[string]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}}, }, } @@ -538,7 +530,11 @@ func TestPurposeVendorExceptions(t *testing.T) { value, present := accountGDPR.PurposeVendorExceptions(tt.givePurpose) assert.Equal(t, tt.wantExceptionMap, value, tt.description) - assert.Equal(t, tt.wantExceptionMapSet, present, tt.description) + if tt.wantExceptionMap == nil { + assert.Equal(t, false, present) + } else { + assert.Equal(t, true, present) + } } } @@ -795,7 +791,6 @@ func TestModulesGetConfig(t *testing.T) { } func TestAccountPriceFloorsValidate(t *testing.T) { - tests := []struct { description string pf *AccountPriceFloors @@ -807,12 +802,22 @@ func TestAccountPriceFloorsValidate(t *testing.T) { EnforceFloorsRate: 100, MaxRule: 200, MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + }, }, }, { description: "Invalid configuration: EnforceFloorRate:110", pf: &AccountPriceFloors{ EnforceFloorsRate: 110, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + }, }, want: []error{errors.New("account_defaults.price_floors.enforce_floors_rate should be between 0 and 100")}, }, @@ -820,6 +825,11 @@ func TestAccountPriceFloorsValidate(t *testing.T) { description: "Invalid configuration: EnforceFloorRate:-10", pf: &AccountPriceFloors{ EnforceFloorsRate: -10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + }, }, want: []error{errors.New("account_defaults.price_floors.enforce_floors_rate should be between 0 and 100")}, }, @@ -827,6 +837,11 @@ func TestAccountPriceFloorsValidate(t *testing.T) { description: "Invalid configuration: MaxRule:-20", pf: &AccountPriceFloors{ MaxRule: -20, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + }, }, want: []error{errors.New("account_defaults.price_floors.max_rules should be between 0 and 2147483647")}, }, @@ -834,9 +849,116 @@ func TestAccountPriceFloorsValidate(t *testing.T) { description: "Invalid configuration: MaxSchemaDims:100", pf: &AccountPriceFloors{ MaxSchemaDims: 100, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + }, }, want: []error{errors.New("account_defaults.price_floors.max_schema_dims should be between 0 and 20")}, }, + { + description: "Invalid period for fetch", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 100, + MaxAge: 600, + Timeout: 12, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.period_sec should not be less than 300 seconds")}, + }, + { + description: "Invalid max age for fetch", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 500, + Timeout: 12, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.max_age_sec should not be less than 600 seconds and greater than maximum integer value")}, + }, + { + description: "Period is greater than max age", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 700, + MaxAge: 600, + Timeout: 12, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.period_sec should be less than account_defaults.price_floors.fetch.max_age_sec")}, + }, + { + description: "Invalid timeout", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 4, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.timeout_ms should be between 10 to 10,000 miliseconds")}, + }, + { + description: "Invalid Max Rules", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + MaxRules: -2, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.max_rules should be greater than or equal to 0")}, + }, + { + description: "Invalid Max File size", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + MaxFileSizeKB: -1, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.max_file_size_kb should be greater than or equal to 0")}, + }, + { + description: "Invalid max_schema_dims", + pf: &AccountPriceFloors{ + EnforceFloorsRate: 100, + MaxRule: 200, + MaxSchemaDims: 10, + Fetcher: AccountFloorFetch{ + Period: 300, + MaxAge: 600, + Timeout: 12, + MaxFileSizeKB: 10, + MaxSchemaDims: 40, + }, + }, + want: []error{errors.New("account_defaults.price_floors.fetch.max_schema_dims should not be less than 0 and greater than 20")}, + }, } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { diff --git a/config/bidderinfo.go b/config/bidderinfo.go index f1f437c5c93..d553df1f4e5 100644 --- a/config/bidderinfo.go +++ b/config/bidderinfo.go @@ -9,8 +9,8 @@ import ( "strings" "text/template" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" validator "github.com/asaskevich/govalidator" "gopkg.in/yaml.v3" @@ -124,6 +124,20 @@ type Syncer struct { // SupportCORS identifies if CORS is supported for the user syncing endpoints. SupportCORS *bool `yaml:"supportCors" mapstructure:"support_cors"` + + // FormatOverride allows a bidder to override their callback type "b" for iframe, "i" for redirect + FormatOverride string `yaml:"formatOverride" mapstructure:"format_override"` + + // Enabled signifies whether a bidder is enabled/disabled for user sync + Enabled *bool `yaml:"enabled" mapstructure:"enabled"` + + // SkipWhen allows bidders to specify when they don't want to sync + SkipWhen *SkipWhen `yaml:"skipwhen" mapstructure:"skipwhen"` +} + +type SkipWhen struct { + GDPR bool `yaml:"gdpr" mapstructure:"gdpr"` + GPPSID []string `yaml:"gpp_sid" mapstructure:"gpp_sid"` } // SyncerEndpoint specifies the configuration of the URL returned by the /cookie_sync endpoint @@ -187,6 +201,21 @@ func (bi BidderInfo) IsEnabled() bool { return !bi.Disabled } +// Defined returns true if at least one field exists, except for the supports field. +func (s *Syncer) Defined() bool { + if s == nil { + return false + } + + return s.Key != "" || + s.IFrame != nil || + s.Redirect != nil || + s.ExternalURL != "" || + s.SupportCORS != nil || + s.FormatOverride != "" || + s.SkipWhen != nil +} + type InfoReader interface { Read() (map[string][]byte, error) } @@ -195,6 +224,11 @@ type InfoReaderFromDisk struct { Path string } +const ( + SyncResponseFormatIFrame = "b" // b = blank HTML response + SyncResponseFormatRedirect = "i" // i = image response +) + func (r InfoReaderFromDisk) Read() (map[string][]byte, error) { bidderConfigs, err := os.ReadDir(r.Path) if err != nil { @@ -226,7 +260,7 @@ func LoadBidderInfo(reader InfoReader) (BidderInfos, error) { return processBidderInfos(reader, openrtb_ext.NormalizeBidderName) } -func processBidderInfos(reader InfoReader, normalizeBidderName func(string) (openrtb_ext.BidderName, bool)) (BidderInfos, error) { +func processBidderInfos(reader InfoReader, normalizeBidderName openrtb_ext.BidderNameNormalizer) (BidderInfos, error) { bidderConfigs, err := reader.Read() if err != nil { return nil, fmt.Errorf("error loading bidders data") @@ -316,7 +350,7 @@ func processBidderAliases(aliasNillableFieldsByBidder map[string]aliasNillableFi if aliasBidderInfo.PlatformID == "" { aliasBidderInfo.PlatformID = parentBidderInfo.PlatformID } - if aliasBidderInfo.Syncer == nil && parentBidderInfo.Syncer != nil { + if aliasBidderInfo.Syncer == nil && parentBidderInfo.Syncer.Defined() { syncerKey := aliasBidderInfo.AliasOf if parentBidderInfo.Syncer.Key != "" { syncerKey = parentBidderInfo.Syncer.Key @@ -551,6 +585,10 @@ func validateSyncer(bidderInfo BidderInfo) error { return nil } + if bidderInfo.Syncer.FormatOverride != SyncResponseFormatIFrame && bidderInfo.Syncer.FormatOverride != SyncResponseFormatRedirect && bidderInfo.Syncer.FormatOverride != "" { + return fmt.Errorf("syncer could not be created, invalid format override value: %s", bidderInfo.Syncer.FormatOverride) + } + for _, supports := range bidderInfo.Syncer.Supports { if !strings.EqualFold(supports, "iframe") && !strings.EqualFold(supports, "redirect") { return fmt.Errorf("syncer could not be created, invalid supported endpoint: %s", supports) @@ -560,58 +598,79 @@ func validateSyncer(bidderInfo BidderInfo) error { return nil } -func applyBidderInfoConfigOverrides(configBidderInfos BidderInfos, fsBidderInfos BidderInfos, normalizeBidderName func(string) (openrtb_ext.BidderName, bool)) (BidderInfos, error) { - for bidderName, bidderInfo := range configBidderInfos { - normalizedBidderName, bidderNameExists := normalizeBidderName(bidderName) - if !bidderNameExists { +func applyBidderInfoConfigOverrides(configBidderInfos nillableFieldBidderInfos, fsBidderInfos BidderInfos, normalizeBidderName openrtb_ext.BidderNameNormalizer) (BidderInfos, error) { + mergedBidderInfos := make(map[string]BidderInfo, len(fsBidderInfos)) + + for bidderName, configBidderInfo := range configBidderInfos { + normalizedBidderName, exists := normalizeBidderName(bidderName) + if !exists { return nil, fmt.Errorf("error setting configuration for bidder %s: unknown bidder", bidderName) } - if fsBidderCfg, exists := fsBidderInfos[string(normalizedBidderName)]; exists { - bidderInfo.Syncer = bidderInfo.Syncer.Override(fsBidderCfg.Syncer) + fsBidderInfo, exists := fsBidderInfos[string(normalizedBidderName)] + if !exists { + return nil, fmt.Errorf("error finding configuration for bidder %s: unknown bidder", bidderName) + } - if bidderInfo.Endpoint == "" && len(fsBidderCfg.Endpoint) > 0 { - bidderInfo.Endpoint = fsBidderCfg.Endpoint - } - if bidderInfo.ExtraAdapterInfo == "" && len(fsBidderCfg.ExtraAdapterInfo) > 0 { - bidderInfo.ExtraAdapterInfo = fsBidderCfg.ExtraAdapterInfo - } - if bidderInfo.Maintainer == nil && fsBidderCfg.Maintainer != nil { - bidderInfo.Maintainer = fsBidderCfg.Maintainer - } - if bidderInfo.Capabilities == nil && fsBidderCfg.Capabilities != nil { - bidderInfo.Capabilities = fsBidderCfg.Capabilities - } - if bidderInfo.Debug == nil && fsBidderCfg.Debug != nil { - bidderInfo.Debug = fsBidderCfg.Debug - } - if bidderInfo.GVLVendorID == 0 && fsBidderCfg.GVLVendorID > 0 { - bidderInfo.GVLVendorID = fsBidderCfg.GVLVendorID - } - if bidderInfo.XAPI.Username == "" && fsBidderCfg.XAPI.Username != "" { - bidderInfo.XAPI.Username = fsBidderCfg.XAPI.Username - } - if bidderInfo.XAPI.Password == "" && fsBidderCfg.XAPI.Password != "" { - bidderInfo.XAPI.Password = fsBidderCfg.XAPI.Password - } - if bidderInfo.XAPI.Tracker == "" && fsBidderCfg.XAPI.Tracker != "" { - bidderInfo.XAPI.Tracker = fsBidderCfg.XAPI.Tracker - } - if bidderInfo.PlatformID == "" && fsBidderCfg.PlatformID != "" { - bidderInfo.PlatformID = fsBidderCfg.PlatformID - } - if bidderInfo.AppSecret == "" && fsBidderCfg.AppSecret != "" { - bidderInfo.AppSecret = fsBidderCfg.AppSecret - } - if bidderInfo.EndpointCompression == "" && fsBidderCfg.EndpointCompression != "" { - bidderInfo.EndpointCompression = fsBidderCfg.EndpointCompression - } + mergedBidderInfo := fsBidderInfo + mergedBidderInfo.Syncer = configBidderInfo.bidderInfo.Syncer.Override(fsBidderInfo.Syncer) + if len(configBidderInfo.bidderInfo.Endpoint) > 0 { + mergedBidderInfo.Endpoint = configBidderInfo.bidderInfo.Endpoint + } + if len(configBidderInfo.bidderInfo.ExtraAdapterInfo) > 0 { + mergedBidderInfo.ExtraAdapterInfo = configBidderInfo.bidderInfo.ExtraAdapterInfo + } + if configBidderInfo.bidderInfo.Maintainer != nil { + mergedBidderInfo.Maintainer = configBidderInfo.bidderInfo.Maintainer + } + if configBidderInfo.bidderInfo.Capabilities != nil { + mergedBidderInfo.Capabilities = configBidderInfo.bidderInfo.Capabilities + } + if configBidderInfo.bidderInfo.Debug != nil { + mergedBidderInfo.Debug = configBidderInfo.bidderInfo.Debug + } + if configBidderInfo.bidderInfo.GVLVendorID > 0 { + mergedBidderInfo.GVLVendorID = configBidderInfo.bidderInfo.GVLVendorID + } + if configBidderInfo.bidderInfo.XAPI.Username != "" { + mergedBidderInfo.XAPI.Username = configBidderInfo.bidderInfo.XAPI.Username + } + if configBidderInfo.bidderInfo.XAPI.Password != "" { + mergedBidderInfo.XAPI.Password = configBidderInfo.bidderInfo.XAPI.Password + } + if configBidderInfo.bidderInfo.XAPI.Tracker != "" { + mergedBidderInfo.XAPI.Tracker = configBidderInfo.bidderInfo.XAPI.Tracker + } + if configBidderInfo.bidderInfo.PlatformID != "" { + mergedBidderInfo.PlatformID = configBidderInfo.bidderInfo.PlatformID + } + if configBidderInfo.bidderInfo.AppSecret != "" { + mergedBidderInfo.AppSecret = configBidderInfo.bidderInfo.AppSecret + } + if configBidderInfo.nillableFields.Disabled != nil { + mergedBidderInfo.Disabled = configBidderInfo.bidderInfo.Disabled + } + if configBidderInfo.nillableFields.ModifyingVastXmlAllowed != nil { + mergedBidderInfo.ModifyingVastXmlAllowed = configBidderInfo.bidderInfo.ModifyingVastXmlAllowed + } + if configBidderInfo.bidderInfo.Experiment.AdsCert.Enabled { + mergedBidderInfo.Experiment.AdsCert.Enabled = true + } + if configBidderInfo.bidderInfo.EndpointCompression != "" { + mergedBidderInfo.EndpointCompression = configBidderInfo.bidderInfo.EndpointCompression + } + if configBidderInfo.bidderInfo.OpenRTB != nil { + mergedBidderInfo.OpenRTB = configBidderInfo.bidderInfo.OpenRTB + } - fsBidderInfos[string(normalizedBidderName)] = bidderInfo - } else { - return nil, fmt.Errorf("error finding configuration for bidder %s: unknown bidder", bidderName) + mergedBidderInfos[string(normalizedBidderName)] = mergedBidderInfo + } + for bidderName, fsBidderInfo := range fsBidderInfos { + if _, exists := mergedBidderInfos[bidderName]; !exists { + mergedBidderInfos[bidderName] = fsBidderInfo } } - return fsBidderInfos, nil + + return mergedBidderInfos, nil } // Override returns a new Syncer object where values in the original are replaced by non-empty/non-default diff --git a/config/bidderinfo_test.go b/config/bidderinfo_test.go index 7b134314609..9ad64ee213f 100644 --- a/config/bidderinfo_test.go +++ b/config/bidderinfo_test.go @@ -7,7 +7,8 @@ import ( "gopkg.in/yaml.v3" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -401,6 +402,15 @@ func TestProcessAliasBidderInfo(t *testing.T) { Key: "bidderA", } + parentWithSyncerSupports := parentWithoutSyncerKey + parentWithSyncerSupports.Syncer = &Syncer{ + Supports: []string{"iframe"}, + } + + aliasWithoutSyncer := parentWithoutSyncerKey + aliasWithoutSyncer.AliasOf = "bidderA" + aliasWithoutSyncer.Syncer = nil + testCases := []struct { description string aliasInfos map[string]aliasNillableFields @@ -428,6 +438,26 @@ func TestProcessAliasBidderInfo(t *testing.T) { expectedErr: nil, expectedBidderInfos: BidderInfos{"bidderA": parentWithSyncerKey, "bidderB": bidderB}, }, + { + description: "inherit all parent info in alias bidder, except for syncer is parent only defines supports", + aliasInfos: map[string]aliasNillableFields{ + "bidderB": { + Disabled: nil, + ModifyingVastXmlAllowed: nil, + Experiment: nil, + XAPI: nil, + }, + }, + bidderInfos: BidderInfos{ + "bidderA": parentWithSyncerSupports, + "bidderB": BidderInfo{ + AliasOf: "bidderA", + // all other fields should be inherited from parent bidder, except for syncer + }, + }, + expectedErr: nil, + expectedBidderInfos: BidderInfos{"bidderA": parentWithSyncerSupports, "bidderB": aliasWithoutSyncer}, + }, { description: "inherit all parent info in alias bidder, use parent name as syncer alias key", aliasInfos: map[string]aliasNillableFields{ @@ -593,6 +623,7 @@ func TestBidderInfoValidationPositive(t *testing.T) { URL: "http://bidderB.com/usersync", UserMacro: "UID", }, + FormatOverride: SyncResponseFormatRedirect, }, }, "bidderC": BidderInfo{ @@ -627,6 +658,9 @@ func TestBidderInfoValidationPositive(t *testing.T) { }, }, }, + Syncer: &Syncer{ + FormatOverride: SyncResponseFormatIFrame, + }, }, } errs := bidderInfos.validate(make([]error, 0)) @@ -1318,6 +1352,37 @@ func TestBidderInfoValidationNegative(t *testing.T) { errors.New("parent bidder: bidderC not found for an alias: bidderB"), }, }, + { + "Invalid format override value", + BidderInfos{ + "bidderB": BidderInfo{ + Endpoint: "http://bidderA.com/openrtb2", + Maintainer: &MaintainerInfo{ + Email: "maintainer@bidderA.com", + }, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeNative, + }, + }, + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{ + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeNative, + }, + }, + }, + Syncer: &Syncer{ + FormatOverride: "x", + }, + }, + }, + []error{ + errors.New("syncer could not be created, invalid format override value: x"), + }, + }, } for _, test := range testCases { @@ -1487,11 +1552,86 @@ func TestSyncerEndpointOverride(t *testing.T) { } } +func TestSyncerDefined(t *testing.T) { + testCases := []struct { + name string + givenSyncer *Syncer + expected bool + }{ + { + name: "nil", + givenSyncer: nil, + expected: false, + }, + { + name: "empty", + givenSyncer: &Syncer{}, + expected: false, + }, + { + name: "key-only", + givenSyncer: &Syncer{Key: "anyKey"}, + expected: true, + }, + { + name: "iframe-only", + givenSyncer: &Syncer{IFrame: &SyncerEndpoint{}}, + expected: true, + }, + { + name: "redirect-only", + givenSyncer: &Syncer{Redirect: &SyncerEndpoint{}}, + expected: true, + }, + { + name: "externalurl-only", + givenSyncer: &Syncer{ExternalURL: "anyURL"}, + expected: true, + }, + { + name: "supportscors-only", + givenSyncer: &Syncer{SupportCORS: ptrutil.ToPtr(false)}, + expected: true, + }, + { + name: "formatoverride-only", + givenSyncer: &Syncer{FormatOverride: "anyFormat"}, + expected: true, + }, + { + name: "skipwhen-only", + givenSyncer: &Syncer{SkipWhen: &SkipWhen{}}, + expected: true, + }, + { + name: "supports-only", + givenSyncer: &Syncer{Supports: []string{"anySupports"}}, + expected: false, + }, + { + name: "supports-with-other", + givenSyncer: &Syncer{Key: "anyKey", Supports: []string{"anySupports"}}, + expected: true, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + result := test.givenSyncer.Defined() + assert.Equal(t, test.expected, result) + }) + } +} + func TestApplyBidderInfoConfigSyncerOverrides(t *testing.T) { var ( givenFileSystem = BidderInfos{"a": {Syncer: &Syncer{Key: "original"}}} - givenConfig = BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}} - expected = BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}} + givenConfig = nillableFieldBidderInfos{ + "a": { + bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}, + }, + } + expected = BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}} ) result, resultErr := applyBidderInfoConfigOverrides(givenConfig, givenFileSystem, mockNormalizeBidderName) @@ -1500,47 +1640,49 @@ func TestApplyBidderInfoConfigSyncerOverrides(t *testing.T) { } func TestApplyBidderInfoConfigOverrides(t *testing.T) { + falseValue := false + var testCases = []struct { description string givenFsBidderInfos BidderInfos - givenConfigBidderInfos BidderInfos + givenConfigBidderInfos nillableFieldBidderInfos expectedError string expectedBidderInfos BidderInfos }{ { description: "Don't override endpoint", givenFsBidderInfos: BidderInfos{"a": {Endpoint: "original"}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {Endpoint: "original", Syncer: &Syncer{Key: "override"}}}, }, { description: "Override endpoint", givenFsBidderInfos: BidderInfos{"a": {Endpoint: "original"}}, - givenConfigBidderInfos: BidderInfos{"a": {Endpoint: "override", Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Endpoint: "override", Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {Endpoint: "override", Syncer: &Syncer{Key: "override"}}}, }, { description: "Don't override ExtraAdapterInfo", givenFsBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "original"}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "original", Syncer: &Syncer{Key: "override"}}}, }, { description: "Override ExtraAdapterInfo", givenFsBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "original"}}, - givenConfigBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "override", Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{ExtraAdapterInfo: "override", Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {ExtraAdapterInfo: "override", Syncer: &Syncer{Key: "override"}}}, }, { description: "Don't override Maintainer", givenFsBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "original"}}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "original"}, Syncer: &Syncer{Key: "override"}}}, }, { description: "Override maintainer", givenFsBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "original"}}}, - givenConfigBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "override"}, Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Maintainer: &MaintainerInfo{Email: "override"}, Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {Maintainer: &MaintainerInfo{Email: "override"}, Syncer: &Syncer{Key: "override"}}}, }, { @@ -1548,7 +1690,7 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { givenFsBidderInfos: BidderInfos{"a": { Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}}}, }}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": { Syncer: &Syncer{Key: "override"}, Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}}}, @@ -1559,10 +1701,10 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { givenFsBidderInfos: BidderInfos{"a": { Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}}}, }}, - givenConfigBidderInfos: BidderInfos{"a": { + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{ Syncer: &Syncer{Key: "override"}, Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}}}, - }}, + }}}, expectedBidderInfos: BidderInfos{"a": { Syncer: &Syncer{Key: "override"}, Capabilities: &CapabilitiesInfo{App: &PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}}}, @@ -1571,25 +1713,25 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { { description: "Don't override Debug", givenFsBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: true}}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: true}, Syncer: &Syncer{Key: "override"}}}, }, { description: "Override Debug", givenFsBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: true}}}, - givenConfigBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: false}, Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Debug: &DebugInfo{Allow: false}, Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {Debug: &DebugInfo{Allow: false}, Syncer: &Syncer{Key: "override"}}}, }, { description: "Don't override GVLVendorID", givenFsBidderInfos: BidderInfos{"a": {GVLVendorID: 5}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {GVLVendorID: 5, Syncer: &Syncer{Key: "override"}}}, }, { description: "Override GVLVendorID", givenFsBidderInfos: BidderInfos{"a": {}}, - givenConfigBidderInfos: BidderInfos{"a": {GVLVendorID: 5, Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{GVLVendorID: 5, Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {GVLVendorID: 5, Syncer: &Syncer{Key: "override"}}}, }, { @@ -1597,7 +1739,7 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { givenFsBidderInfos: BidderInfos{"a": { XAPI: AdapterXAPI{Username: "username1", Password: "password2", Tracker: "tracker3"}, }}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": { XAPI: AdapterXAPI{Username: "username1", Password: "password2", Tracker: "tracker3"}, Syncer: &Syncer{Key: "override"}}}, @@ -1606,9 +1748,9 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { description: "Override XAPI", givenFsBidderInfos: BidderInfos{"a": { XAPI: AdapterXAPI{Username: "username", Password: "password", Tracker: "tracker"}}}, - givenConfigBidderInfos: BidderInfos{"a": { + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{ XAPI: AdapterXAPI{Username: "username1", Password: "password2", Tracker: "tracker3"}, - Syncer: &Syncer{Key: "override"}}}, + Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": { XAPI: AdapterXAPI{Username: "username1", Password: "password2", Tracker: "tracker3"}, Syncer: &Syncer{Key: "override"}}}, @@ -1616,39 +1758,93 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { { description: "Don't override PlatformID", givenFsBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID"}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID", Syncer: &Syncer{Key: "override"}}}, }, { description: "Override PlatformID", givenFsBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID1"}}, - givenConfigBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID2", Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{PlatformID: "PlatformID2", Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {PlatformID: "PlatformID2", Syncer: &Syncer{Key: "override"}}}, }, { description: "Don't override AppSecret", givenFsBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret"}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret", Syncer: &Syncer{Key: "override"}}}, }, { description: "Override AppSecret", givenFsBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret1"}}, - givenConfigBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret2", Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{AppSecret: "AppSecret2", Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {AppSecret: "AppSecret2", Syncer: &Syncer{Key: "override"}}}, }, { description: "Don't override EndpointCompression", givenFsBidderInfos: BidderInfos{"a": {EndpointCompression: "GZIP"}}, - givenConfigBidderInfos: BidderInfos{"a": {Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {EndpointCompression: "GZIP", Syncer: &Syncer{Key: "override"}}}, }, { description: "Override EndpointCompression", givenFsBidderInfos: BidderInfos{"a": {EndpointCompression: "GZIP"}}, - givenConfigBidderInfos: BidderInfos{"a": {EndpointCompression: "LZ77", Syncer: &Syncer{Key: "override"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{EndpointCompression: "LZ77", Syncer: &Syncer{Key: "override"}}}}, expectedBidderInfos: BidderInfos{"a": {EndpointCompression: "LZ77", Syncer: &Syncer{Key: "override"}}}, }, + { + description: "Don't override Disabled", + givenFsBidderInfos: BidderInfos{"a": {Disabled: true}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Disabled: false, Syncer: &Syncer{Key: "override"}}, nillableFields: bidderInfoNillableFields{Disabled: nil}}}, + expectedBidderInfos: BidderInfos{"a": {Disabled: true, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Override Disabled", + givenFsBidderInfos: BidderInfos{"a": {Disabled: true}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Disabled: false, Syncer: &Syncer{Key: "override"}}, nillableFields: bidderInfoNillableFields{Disabled: &falseValue}}}, + expectedBidderInfos: BidderInfos{"a": {Disabled: false, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Don't override ModifyingVastXmlAllowed", + givenFsBidderInfos: BidderInfos{"a": {ModifyingVastXmlAllowed: true}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{ModifyingVastXmlAllowed: false, Syncer: &Syncer{Key: "override"}}, nillableFields: bidderInfoNillableFields{ModifyingVastXmlAllowed: nil}}}, + expectedBidderInfos: BidderInfos{"a": {ModifyingVastXmlAllowed: true, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Override ModifyingVastXmlAllowed", + givenFsBidderInfos: BidderInfos{"a": {ModifyingVastXmlAllowed: true}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{ModifyingVastXmlAllowed: false, Syncer: &Syncer{Key: "override"}}, nillableFields: bidderInfoNillableFields{ModifyingVastXmlAllowed: &falseValue}}}, + expectedBidderInfos: BidderInfos{"a": {ModifyingVastXmlAllowed: false, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Don't override OpenRTB", + givenFsBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "1"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}}}, + expectedBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "1"}, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Override OpenRTB", + givenFsBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "1"}}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{OpenRTB: &OpenRTBInfo{Version: "2"}, Syncer: &Syncer{Key: "override"}}}}, + expectedBidderInfos: BidderInfos{"a": {OpenRTB: &OpenRTBInfo{Version: "2"}, Syncer: &Syncer{Key: "override"}}}, + }, + { + description: "Don't override AliasOf", + givenFsBidderInfos: BidderInfos{"a": {AliasOf: "Alias1"}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{}}}, + expectedBidderInfos: BidderInfos{"a": {AliasOf: "Alias1"}}, + }, + { + description: "Attempt override AliasOf but ignored", + givenFsBidderInfos: BidderInfos{"a": {AliasOf: "Alias1"}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{AliasOf: "Alias2"}}}, + expectedBidderInfos: BidderInfos{"a": {AliasOf: "Alias1"}}, + }, + { + description: "Two bidder infos: One with overrides and one without", + givenFsBidderInfos: BidderInfos{"a": {Endpoint: "original"}, "b": {Endpoint: "b endpoint"}}, + givenConfigBidderInfos: nillableFieldBidderInfos{"a": {bidderInfo: BidderInfo{Endpoint: "override", Syncer: &Syncer{Key: "override"}}}}, + expectedBidderInfos: BidderInfos{"a": {Endpoint: "override", Syncer: &Syncer{Key: "override"}}, "b": {Endpoint: "b endpoint"}}, + }, } for _, test := range testCases { bidderInfos, resultErr := applyBidderInfoConfigOverrides(test.givenConfigBidderInfos, test.givenFsBidderInfos, mockNormalizeBidderName) @@ -1659,26 +1855,31 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { func TestApplyBidderInfoConfigOverridesInvalid(t *testing.T) { var testCases = []struct { - description string - givenFsBidderInfos BidderInfos - givenConfigBidderInfos BidderInfos - expectedError string - expectedBidderInfos BidderInfos + description string + givenFsBidderInfos BidderInfos + givenNillableFieldBidderInfos nillableFieldBidderInfos + expectedError string + expectedBidderInfos BidderInfos }{ { - description: "Bidder doesn't exists in bidder list", - givenConfigBidderInfos: BidderInfos{"unknown": {Syncer: &Syncer{Key: "override"}}}, - expectedError: "error setting configuration for bidder unknown: unknown bidder", + description: "Bidder doesn't exists in bidder list", + givenNillableFieldBidderInfos: nillableFieldBidderInfos{"unknown": nillableFieldBidderInfo{ + bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}, + }}, + expectedError: "error setting configuration for bidder unknown: unknown bidder", }, { - description: "Bidder doesn't exists in file system", - givenFsBidderInfos: BidderInfos{"unknown": {Endpoint: "original"}}, - givenConfigBidderInfos: BidderInfos{"bidderA": {Syncer: &Syncer{Key: "override"}}}, - expectedError: "error finding configuration for bidder bidderA: unknown bidder", + description: "Bidder doesn't exists in file system", + givenFsBidderInfos: BidderInfos{"unknown": {Endpoint: "original"}}, + givenNillableFieldBidderInfos: nillableFieldBidderInfos{"bidderA": nillableFieldBidderInfo{ + bidderInfo: BidderInfo{Syncer: &Syncer{Key: "override"}}, + }}, + expectedError: "error finding configuration for bidder bidderA: unknown bidder", }, } for _, test := range testCases { - _, err := applyBidderInfoConfigOverrides(test.givenConfigBidderInfos, test.givenFsBidderInfos, mockNormalizeBidderName) + + _, err := applyBidderInfoConfigOverrides(test.givenNillableFieldBidderInfos, test.givenFsBidderInfos, mockNormalizeBidderName) assert.ErrorContains(t, err, test.expectedError, test.description+":err") } } @@ -1690,7 +1891,19 @@ func TestReadFullYamlBidderConfig(t *testing.T) { err := yaml.Unmarshal([]byte(fullBidderYAMLConfig), &bidderInf) require.NoError(t, err) - actualBidderInfo, err := applyBidderInfoConfigOverrides(BidderInfos{bidder: bidderInf}, BidderInfos{bidder: {Syncer: &Syncer{Supports: []string{"iframe"}}}}, mockNormalizeBidderName) + bidderInfoOverrides := nillableFieldBidderInfos{ + bidder: nillableFieldBidderInfo{ + bidderInfo: bidderInf, + nillableFields: bidderInfoNillableFields{ + Disabled: &bidderInf.Disabled, + ModifyingVastXmlAllowed: &bidderInf.ModifyingVastXmlAllowed, + }, + }, + } + bidderInfoBase := BidderInfos{ + bidder: {Syncer: &Syncer{Supports: []string{"iframe"}}}, + } + actualBidderInfo, err := applyBidderInfoConfigOverrides(bidderInfoOverrides, bidderInfoBase, mockNormalizeBidderName) require.NoError(t, err) expectedBidderInfo := BidderInfos{ diff --git a/config/compression.go b/config/compression.go index db85202b4a8..c838e3390a2 100644 --- a/config/compression.go +++ b/config/compression.go @@ -1,6 +1,6 @@ package config -import "github.com/prebid/prebid-server/util/httputil" +import "github.com/prebid/prebid-server/v3/util/httputil" type Compression struct { Request CompressionInfo `mapstructure:"request"` diff --git a/config/compression_test.go b/config/compression_test.go index cd9048cd99e..78e7b6468e0 100644 --- a/config/compression_test.go +++ b/config/compression_test.go @@ -3,7 +3,7 @@ package config import ( "testing" - "github.com/prebid/prebid-server/util/httputil" + "github.com/prebid/prebid-server/v3/util/httputil" "github.com/stretchr/testify/assert" ) diff --git a/config/config.go b/config/config.go index 4bd7f807002..30f8d69adc1 100644 --- a/config/config.go +++ b/config/config.go @@ -11,10 +11,10 @@ import ( "github.com/golang/glog" "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" "github.com/spf13/viper" ) @@ -27,6 +27,7 @@ type Configuration struct { UnixSocketName string `mapstructure:"unix_socket_name"` Client HTTPClient `mapstructure:"http_client"` CacheClient HTTPClient `mapstructure:"http_client_cache"` + Admin Admin `mapstructure:"admin"` AdminPort int `mapstructure:"admin_port"` Compression Compression `mapstructure:"compression"` // GarbageCollectorThreshold allocates virtual memory (in bytes) which is not used by PBS but @@ -53,6 +54,8 @@ type Configuration struct { // Note that StoredVideo refers to stored video requests, and has nothing to do with caching video creatives. StoredVideo StoredRequests `mapstructure:"stored_video_req"` StoredResponses StoredRequests `mapstructure:"stored_responses"` + // StoredRequestsTimeout defines the number of milliseconds before a timeout occurs with stored requests fetch + StoredRequestsTimeout int `mapstructure:"stored_requests_timeout_ms"` MaxRequestSize int64 `mapstructure:"max_request_size"` Analytics Analytics `mapstructure:"analytics"` @@ -65,9 +68,9 @@ type Configuration struct { VideoStoredRequestRequired bool `mapstructure:"video_stored_request_required"` - // Array of blacklisted apps that is used to create the hash table BlacklistedAppMap so App.ID's can be instantly accessed. - BlacklistedApps []string `mapstructure:"blacklisted_apps,flow"` - BlacklistedAppMap map[string]bool + // Array of blocked apps that is used to create the hash table BlockedAppsLookup so App.ID's can be instantly accessed. + BlockedApps []string `mapstructure:"blocked_apps,flow"` + BlockedAppsLookup map[string]bool // Is publisher/account ID required to be submitted in the OpenRTB2 request AccountRequired bool `mapstructure:"account_required"` // AccountDefaults defines default settings for valid accounts that are partially defined @@ -102,9 +105,21 @@ type Configuration struct { PriceFloors PriceFloors `mapstructure:"price_floors"` } -type PriceFloors struct { +type Admin struct { Enabled bool `mapstructure:"enabled"` } +type PriceFloors struct { + Enabled bool `mapstructure:"enabled"` + Fetcher PriceFloorFetcher `mapstructure:"fetcher"` +} + +type PriceFloorFetcher struct { + HttpClient HTTPClient `mapstructure:"http_client"` + CacheSize int `mapstructure:"cache_size_mb"` + Worker int `mapstructure:"worker"` + Capacity int `mapstructure:"capacity"` + MaxRetries int `mapstructure:"max_retries"` +} const MIN_COOKIE_SIZE_BYTES = 500 @@ -119,6 +134,9 @@ func (cfg *Configuration) validate(v *viper.Viper) []error { var errs []error errs = cfg.AuctionTimeouts.validate(errs) errs = cfg.StoredRequests.validate(errs) + if cfg.StoredRequestsTimeout <= 0 { + errs = append(errs, fmt.Errorf("cfg.stored_requests_timeout_ms must be > 0. Got %d", cfg.StoredRequestsTimeout)) + } errs = cfg.StoredRequestsAMP.validate(errs) errs = cfg.Accounts.validate(errs) errs = cfg.CategoryMapping.validate(errs) @@ -140,10 +158,6 @@ func (cfg *Configuration) validate(v *viper.Viper) []error { glog.Warning(`account_defaults.events has no effect as the feature is under development.`) } - if cfg.PriceFloors.Enabled { - glog.Warning(`cfg.PriceFloors.Enabled will currently not do anything as price floors feature is still under development.`) - } - errs = cfg.Experiment.validate(errs) errs = cfg.BidderInfos.validate(errs) errs = cfg.AccountDefaults.Privacy.IPv6Config.Validate(errs) @@ -178,27 +192,27 @@ func (data *ExternalCache) validate(errs []error) []error { // Either host or path or both not empty, validate. if data.Host == "" && data.Path != "" || data.Host != "" && data.Path == "" { - return append(errs, errors.New("External cache Host and Path must both be specified")) + return append(errs, errors.New("external cache Host and Path must both be specified")) } if strings.HasSuffix(data.Host, "/") { - return append(errs, errors.New(fmt.Sprintf("External cache Host '%s' must not end with a path separator", data.Host))) + return append(errs, fmt.Errorf("external cache Host '%s' must not end with a path separator", data.Host)) } if strings.Contains(data.Host, "://") { - return append(errs, errors.New(fmt.Sprintf("External cache Host must not specify a protocol. '%s'", data.Host))) + return append(errs, fmt.Errorf("external cache Host must not specify a protocol. '%s'", data.Host)) } if !strings.HasPrefix(data.Path, "/") { - return append(errs, errors.New(fmt.Sprintf("External cache Path '%s' must begin with a path separator", data.Path))) + return append(errs, fmt.Errorf("external cache Path '%s' must begin with a path separator", data.Path)) } urlObj, err := url.Parse("https://" + data.Host + data.Path) if err != nil { - return append(errs, errors.New(fmt.Sprintf("External cache Path validation error: %s ", err.Error()))) + return append(errs, fmt.Errorf("external cache Path validation error: %s ", err.Error())) } if urlObj.Host != data.Host { - return append(errs, errors.New(fmt.Sprintf("External cache Host '%s' is invalid", data.Host))) + return append(errs, fmt.Errorf("external cache Host '%s' is invalid", data.Host)) } if urlObj.Path != data.Path { - return append(errs, errors.New("External cache Path is invalid")) + return append(errs, fmt.Errorf("external cache Path is invalid")) } return errs @@ -255,7 +269,7 @@ func (cfg *GDPR) validate(v *viper.Viper, errs []error) []error { if cfg.HostVendorID == 0 { glog.Warning("gdpr.host_vendor_id was not specified. Host company GDPR checks will be skipped.") } - if cfg.AMPException == true { + if cfg.AMPException { errs = append(errs, fmt.Errorf("gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)")) } return cfg.validatePurposes(errs) @@ -370,13 +384,13 @@ func (t *TCF2) PurposeEnforcingVendors(purpose consentconstants.Purpose) (enforc // PurposeVendorExceptions returns the vendor exception map for a given purpose if it exists, otherwise it returns // an empty map of vendor exceptions -func (t *TCF2) PurposeVendorExceptions(purpose consentconstants.Purpose) (vendorExceptions map[openrtb_ext.BidderName]struct{}) { +func (t *TCF2) PurposeVendorExceptions(purpose consentconstants.Purpose) (vendorExceptions map[string]struct{}) { c, exists := t.PurposeConfigs[purpose] if exists && c.VendorExceptionMap != nil { return c.VendorExceptionMap } - return make(map[openrtb_ext.BidderName]struct{}, 0) + return make(map[string]struct{}, 0) } // FeatureOneEnforced checks if special feature one is enforced. If it is enforced, PBS will determine whether geo @@ -412,8 +426,8 @@ type TCF2Purpose struct { EnforcePurpose bool `mapstructure:"enforce_purpose"` EnforceVendors bool `mapstructure:"enforce_vendors"` // Array of vendor exceptions that is used to create the hash table VendorExceptionMap so vendor names can be instantly accessed - VendorExceptions []openrtb_ext.BidderName `mapstructure:"vendor_exceptions"` - VendorExceptionMap map[openrtb_ext.BidderName]struct{} + VendorExceptions []string `mapstructure:"vendor_exceptions"` + VendorExceptionMap map[string]struct{} } type TCF2SpecialFeature struct { @@ -437,8 +451,9 @@ type LMT struct { } type Analytics struct { - File FileLogs `mapstructure:"file"` - Pubstack Pubstack `mapstructure:"pubstack"` + File FileLogs `mapstructure:"file"` + Agma AgmaAnalytics `mapstructure:"agma"` + Pubstack Pubstack `mapstructure:"pubstack"` } type CurrencyConverter struct { @@ -454,6 +469,31 @@ func (cfg *CurrencyConverter) validate(errs []error) []error { return errs } +type AgmaAnalytics struct { + Enabled bool `mapstructure:"enabled"` + Endpoint AgmaAnalyticsHttpEndpoint `mapstructure:"endpoint"` + Buffers AgmaAnalyticsBuffer `mapstructure:"buffers"` + Accounts []AgmaAnalyticsAccount `mapstructure:"accounts"` +} + +type AgmaAnalyticsHttpEndpoint struct { + Url string `mapstructure:"url"` + Timeout string `mapstructure:"timeout"` + Gzip bool `mapstructure:"gzip"` +} + +type AgmaAnalyticsBuffer struct { + BufferSize string `mapstructure:"size"` + EventCount int `mapstructure:"count"` + Timeout string `mapstructure:"timeout"` +} + +type AgmaAnalyticsAccount struct { + Code string `mapstructure:"code"` + PublisherId string `mapstructure:"publisher_id"` + SiteAppId string `mapstructure:"site_app_id"` +} + // FileLogs Corresponding config for FileLogger as a PBS Analytics Module type FileLogs struct { Filename string `mapstructure:"filename"` @@ -525,6 +565,9 @@ type DisabledMetrics struct { // that were created or reused. AdapterConnectionMetrics bool `mapstructure:"adapter_connections_metrics"` + // True if we don't want to collect the per adapter buyer UID scrubbed metric + AdapterBuyerUIDScrubbed bool `mapstructure:"adapter_buyeruid_scrubbed"` + // True if we don't want to collect the per adapter GDPR request blocked metric AdapterGDPRRequestBlocked bool `mapstructure:"adapter_gdpr_request_blocked"` @@ -671,7 +714,7 @@ func (cfg *TimeoutNotification) validate(errs []error) []error { } // New uses viper to get our server configurations. -func New(v *viper.Viper, bidderInfos BidderInfos, normalizeBidderName func(string) (openrtb_ext.BidderName, bool)) (*Configuration, error) { +func New(v *viper.Viper, bidderInfos BidderInfos, normalizeBidderName openrtb_ext.BidderNameNormalizer) (*Configuration, error) { var c Configuration if err := v.Unmarshal(&c); err != nil { return nil, fmt.Errorf("viper failed to unmarshal app config: %v", err) @@ -686,6 +729,10 @@ func New(v *viper.Viper, bidderInfos BidderInfos, normalizeBidderName func(strin return nil, err } + if err := UnpackDSADefault(c.AccountDefaults.Privacy.DSA); err != nil { + return nil, fmt.Errorf("invalid default account DSA: %v", err) + } + // Update account defaults and generate base json for patch c.AccountDefaults.CacheTTL = c.CacheURL.DefaultTTLs // comment this out to set explicitly in config @@ -730,13 +777,13 @@ func New(v *viper.Viper, bidderInfos BidderInfos, normalizeBidderName func(strin } } - // To look for a purpose's vendor exceptions in O(1) time, for each purpose we fill this hash table with bidders - // located in the VendorExceptions field of the GDPR.TCF2.PurposeX struct defined in this file + // To look for a purpose's vendor exceptions in O(1) time, for each purpose we fill this hash table with bidders/analytics + // adapters located in the VendorExceptions field of the GDPR.TCF2.PurposeX struct defined in this file for _, pc := range c.GDPR.TCF2.PurposeConfigs { - pc.VendorExceptionMap = make(map[openrtb_ext.BidderName]struct{}) + pc.VendorExceptionMap = make(map[string]struct{}) for v := 0; v < len(pc.VendorExceptions); v++ { - bidderName := pc.VendorExceptions[v] - pc.VendorExceptionMap[bidderName] = struct{}{} + adapterName := pc.VendorExceptions[v] + pc.VendorExceptionMap[adapterName] = struct{}{} } } @@ -749,16 +796,20 @@ func New(v *viper.Viper, bidderInfos BidderInfos, normalizeBidderName func(strin } // To look for a request's app_id in O(1) time, we fill this hash table located in the - // the BlacklistedApps field of the Configuration struct defined in this file - c.BlacklistedAppMap = make(map[string]bool) - for i := 0; i < len(c.BlacklistedApps); i++ { - c.BlacklistedAppMap[c.BlacklistedApps[i]] = true + // the BlockedApps field of the Configuration struct defined in this file + c.BlockedAppsLookup = make(map[string]bool) + for i := 0; i < len(c.BlockedApps); i++ { + c.BlockedAppsLookup[c.BlockedApps[i]] = true } // Migrate combo stored request config to separate stored_reqs and amp stored_reqs configs. resolvedStoredRequestsConfig(&c) - mergedBidderInfos, err := applyBidderInfoConfigOverrides(c.BidderInfos, bidderInfos, normalizeBidderName) + configBidderInfosWithNillableFields, err := setConfigBidderInfoNillableFields(v, c.BidderInfos) + if err != nil { + return nil, err + } + mergedBidderInfos, err := applyBidderInfoConfigOverrides(configBidderInfosWithNillableFields, bidderInfos, normalizeBidderName) if err != nil { return nil, err } @@ -773,6 +824,36 @@ func New(v *viper.Viper, bidderInfos BidderInfos, normalizeBidderName func(strin return &c, nil } +type bidderInfoNillableFields struct { + Disabled *bool `yaml:"disabled" mapstructure:"disabled"` + ModifyingVastXmlAllowed *bool `yaml:"modifyingVastXmlAllowed" mapstructure:"modifyingVastXmlAllowed"` +} +type nillableFieldBidderInfos map[string]nillableFieldBidderInfo +type nillableFieldBidderInfo struct { + nillableFields bidderInfoNillableFields + bidderInfo BidderInfo +} + +func setConfigBidderInfoNillableFields(v *viper.Viper, bidderInfos BidderInfos) (nillableFieldBidderInfos, error) { + if len(bidderInfos) == 0 || v == nil { + return nil, nil + } + infos := make(nillableFieldBidderInfos, len(bidderInfos)) + + for bidderName, bidderInfo := range bidderInfos { + info := nillableFieldBidderInfo{bidderInfo: bidderInfo} + + if err := v.UnmarshalKey("adapters."+bidderName+".disabled", &info.nillableFields.Disabled); err != nil { + return nil, fmt.Errorf("viper failed to unmarshal bidder config disabled: %v", err) + } + if err := v.UnmarshalKey("adapters."+bidderName+".modifyingvastxmlallowed", &info.nillableFields.ModifyingVastXmlAllowed); err != nil { + return nil, fmt.Errorf("viper failed to unmarshal bidder config modifyingvastxmlallowed: %v", err) + } + infos[bidderName] = info + } + return infos, nil +} + // MarshalAccountDefaults compiles AccountDefaults into the JSON format used for merge patch func (cfg *Configuration) MarshalAccountDefaults() error { var err error @@ -782,6 +863,14 @@ func (cfg *Configuration) MarshalAccountDefaults() error { return err } +// UnpackDSADefault validates the JSON DSA default object string by unmarshaling and maps it to a struct +func UnpackDSADefault(dsa *AccountDSA) error { + if dsa == nil || len(dsa.Default) == 0 { + return nil + } + return jsonutil.Unmarshal([]byte(dsa.Default), &dsa.DefaultUnpacked) +} + // AccountDefaultsJSON returns the precompiled JSON form of account_defaults func (cfg *Configuration) AccountDefaultsJSON() json.RawMessage { return cfg.accountDefaultsJSON @@ -819,6 +908,7 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("unix_socket_enable", false) // boolean which decide if the socket-server will be started. v.SetDefault("unix_socket_name", "prebid-server.sock") // path of the socket's file which must be listened. v.SetDefault("admin_port", 6060) + v.SetDefault("admin.enabled", true) // boolean to determine if admin listener will be started. v.SetDefault("garbage_collector_threshold", 0) v.SetDefault("status_response", "") v.SetDefault("datacenter", "") @@ -863,6 +953,7 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("metrics.disabled_metrics.account_debug", true) v.SetDefault("metrics.disabled_metrics.account_stored_responses", true) v.SetDefault("metrics.disabled_metrics.adapter_connections_metrics", true) + v.SetDefault("metrics.disabled_metrics.adapter_buyeruid_scrubbed", true) v.SetDefault("metrics.disabled_metrics.adapter_gdpr_request_blocked", false) v.SetDefault("metrics.influxdb.host", "") v.SetDefault("metrics.influxdb.database", "") @@ -878,6 +969,7 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("category_mapping.filesystem.enabled", true) v.SetDefault("category_mapping.filesystem.directorypath", "./static/category-mapping") v.SetDefault("category_mapping.http.endpoint", "") + v.SetDefault("stored_requests_timeout_ms", 50) v.SetDefault("stored_requests.database.connection.driver", "") v.SetDefault("stored_requests.database.connection.dbname", "") v.SetDefault("stored_requests.database.connection.host", "") @@ -1007,6 +1099,14 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("analytics.pubstack.buffers.size", "2MB") v.SetDefault("analytics.pubstack.buffers.count", 100) v.SetDefault("analytics.pubstack.buffers.timeout", "900s") + v.SetDefault("analytics.agma.enabled", false) + v.SetDefault("analytics.agma.endpoint.url", "https://go.pbs.agma-analytics.de/v1/prebid-server") + v.SetDefault("analytics.agma.endpoint.timeout", "2s") + v.SetDefault("analytics.agma.endpoint.gzip", false) + v.SetDefault("analytics.agma.buffers.size", "2MB") + v.SetDefault("analytics.agma.buffers.count", 100) + v.SetDefault("analytics.agma.buffers.timeout", "15m") + v.SetDefault("analytics.agma.accounts", []AgmaAnalyticsAccount{}) v.SetDefault("amp_timeout_adjustment_ms", 0) v.BindEnv("gdpr.default_value") v.SetDefault("gdpr.enabled", true) @@ -1025,16 +1125,16 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("gdpr.tcf2.purpose8.enforce_vendors", true) v.SetDefault("gdpr.tcf2.purpose9.enforce_vendors", true) v.SetDefault("gdpr.tcf2.purpose10.enforce_vendors", true) - v.SetDefault("gdpr.tcf2.purpose1.vendor_exceptions", []openrtb_ext.BidderName{}) - v.SetDefault("gdpr.tcf2.purpose2.vendor_exceptions", []openrtb_ext.BidderName{}) - v.SetDefault("gdpr.tcf2.purpose3.vendor_exceptions", []openrtb_ext.BidderName{}) - v.SetDefault("gdpr.tcf2.purpose4.vendor_exceptions", []openrtb_ext.BidderName{}) - v.SetDefault("gdpr.tcf2.purpose5.vendor_exceptions", []openrtb_ext.BidderName{}) - v.SetDefault("gdpr.tcf2.purpose6.vendor_exceptions", []openrtb_ext.BidderName{}) - v.SetDefault("gdpr.tcf2.purpose7.vendor_exceptions", []openrtb_ext.BidderName{}) - v.SetDefault("gdpr.tcf2.purpose8.vendor_exceptions", []openrtb_ext.BidderName{}) - v.SetDefault("gdpr.tcf2.purpose9.vendor_exceptions", []openrtb_ext.BidderName{}) - v.SetDefault("gdpr.tcf2.purpose10.vendor_exceptions", []openrtb_ext.BidderName{}) + v.SetDefault("gdpr.tcf2.purpose1.vendor_exceptions", []string{}) + v.SetDefault("gdpr.tcf2.purpose2.vendor_exceptions", []string{}) + v.SetDefault("gdpr.tcf2.purpose3.vendor_exceptions", []string{}) + v.SetDefault("gdpr.tcf2.purpose4.vendor_exceptions", []string{}) + v.SetDefault("gdpr.tcf2.purpose5.vendor_exceptions", []string{}) + v.SetDefault("gdpr.tcf2.purpose6.vendor_exceptions", []string{}) + v.SetDefault("gdpr.tcf2.purpose7.vendor_exceptions", []string{}) + v.SetDefault("gdpr.tcf2.purpose8.vendor_exceptions", []string{}) + v.SetDefault("gdpr.tcf2.purpose9.vendor_exceptions", []string{}) + v.SetDefault("gdpr.tcf2.purpose10.vendor_exceptions", []string{}) v.SetDefault("gdpr.amp_exception", false) v.SetDefault("gdpr.eea_countries", []string{"ALA", "AUT", "BEL", "BGR", "HRV", "CYP", "CZE", "DNK", "EST", "FIN", "FRA", "GUF", "DEU", "GIB", "GRC", "GLP", "GGY", "HUN", "ISL", "IRL", "IMN", "ITA", "JEY", "LVA", @@ -1048,8 +1148,7 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("default_request.type", "") v.SetDefault("default_request.file.name", "") v.SetDefault("default_request.alias_info", false) - v.SetDefault("blacklisted_apps", []string{""}) - v.SetDefault("blacklisted_accts", []string{""}) + v.SetDefault("blocked_apps", []string{""}) v.SetDefault("account_required", false) v.SetDefault("account_defaults.disabled", false) v.SetDefault("account_defaults.debug_allow", true) @@ -1060,9 +1159,35 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("account_defaults.price_floors.use_dynamic_data", false) v.SetDefault("account_defaults.price_floors.max_rules", 100) v.SetDefault("account_defaults.price_floors.max_schema_dims", 3) + v.SetDefault("account_defaults.price_floors.fetch.enabled", false) + v.SetDefault("account_defaults.price_floors.fetch.url", "") + v.SetDefault("account_defaults.price_floors.fetch.timeout_ms", 3000) + v.SetDefault("account_defaults.price_floors.fetch.max_file_size_kb", 100) + v.SetDefault("account_defaults.price_floors.fetch.max_rules", 1000) + v.SetDefault("account_defaults.price_floors.fetch.max_age_sec", 86400) + v.SetDefault("account_defaults.price_floors.fetch.period_sec", 3600) + v.SetDefault("account_defaults.price_floors.fetch.max_schema_dims", 0) + v.SetDefault("account_defaults.privacy.privacysandbox.topicsdomain", "") + v.SetDefault("account_defaults.privacy.privacysandbox.cookiedeprecation.enabled", false) + v.SetDefault("account_defaults.privacy.privacysandbox.cookiedeprecation.ttl_sec", 604800) + + v.SetDefault("account_defaults.events_enabled", false) + v.BindEnv("account_defaults.privacy.dsa.default") + v.BindEnv("account_defaults.privacy.dsa.gdpr_only") v.SetDefault("account_defaults.privacy.ipv6.anon_keep_bits", 56) v.SetDefault("account_defaults.privacy.ipv4.anon_keep_bits", 24) + //Defaults for Price floor fetcher + v.SetDefault("price_floors.fetcher.worker", 20) + v.SetDefault("price_floors.fetcher.capacity", 20000) + v.SetDefault("price_floors.fetcher.cache_size_mb", 64) + v.SetDefault("price_floors.fetcher.http_client.max_connections_per_host", 0) // unlimited + v.SetDefault("price_floors.fetcher.http_client.max_idle_connections", 40) + v.SetDefault("price_floors.fetcher.http_client.max_idle_connections_per_host", 2) + v.SetDefault("price_floors.fetcher.http_client.idle_connection_timeout_seconds", 60) + v.SetDefault("price_floors.fetcher.max_retries", 10) + + v.SetDefault("account_defaults.events_enabled", false) v.SetDefault("compression.response.enable_gzip", false) v.SetDefault("compression.request.enable_gzip", false) diff --git a/config/config_test.go b/config/config_test.go index 21d1681c1e7..a68f478a642 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -5,11 +5,13 @@ import ( "errors" "net" "os" + "strings" "testing" "time" "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/spf13/viper" "github.com/stretchr/testify/assert" ) @@ -156,8 +158,10 @@ func TestDefaults(t *testing.T) { cmpBools(t, "account_debug", true, cfg.Metrics.Disabled.AccountDebug) cmpBools(t, "account_stored_responses", true, cfg.Metrics.Disabled.AccountStoredResponses) cmpBools(t, "adapter_connections_metrics", true, cfg.Metrics.Disabled.AdapterConnectionMetrics) + cmpBools(t, "adapter_buyeruid_scrubbed", true, cfg.Metrics.Disabled.AdapterBuyerUIDScrubbed) cmpBools(t, "adapter_gdpr_request_blocked", false, cfg.Metrics.Disabled.AdapterGDPRRequestBlocked) cmpStrings(t, "certificates_file", "", cfg.PemCertsFile) + cmpInts(t, "stored_requests_timeout_ms", 50, cfg.StoredRequestsTimeout) cmpBools(t, "stored_requests.filesystem.enabled", false, cfg.StoredRequests.Files.Enabled) cmpStrings(t, "stored_requests.filesystem.directorypath", "./stored_requests/data/by_id", cfg.StoredRequests.Files.Path) cmpBools(t, "auto_gen_source_tid", true, cfg.AutoGenSourceTID) @@ -174,6 +178,14 @@ func TestDefaults(t *testing.T) { //Assert the price floor default values cmpBools(t, "price_floors.enabled", false, cfg.PriceFloors.Enabled) + cmpInts(t, "price_floors.fetcher.worker", 20, cfg.PriceFloors.Fetcher.Worker) + cmpInts(t, "price_floors.fetcher.capacity", 20000, cfg.PriceFloors.Fetcher.Capacity) + cmpInts(t, "price_floors.fetcher.cache_size_mb", 64, cfg.PriceFloors.Fetcher.CacheSize) + cmpInts(t, "price_floors.fetcher.http_client.max_connections_per_host", 0, cfg.PriceFloors.Fetcher.HttpClient.MaxConnsPerHost) + cmpInts(t, "price_floors.fetcher.http_client.max_idle_connections", 40, cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConns) + cmpInts(t, "price_floors.fetcher.http_client.max_idle_connections_per_host", 2, cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConnsPerHost) + cmpInts(t, "price_floors.fetcher.http_client.idle_connection_timeout_seconds", 60, cfg.PriceFloors.Fetcher.HttpClient.IdleConnTimeout) + cmpInts(t, "price_floors.fetcher.max_retries", 10, cfg.PriceFloors.Fetcher.MaxRetries) // Assert compression related defaults cmpBools(t, "compression.request.enable_gzip", false, cfg.Compression.Request.GZIP) @@ -186,6 +198,18 @@ func TestDefaults(t *testing.T) { cmpBools(t, "account_defaults.price_floors.use_dynamic_data", false, cfg.AccountDefaults.PriceFloors.UseDynamicData) cmpInts(t, "account_defaults.price_floors.max_rules", 100, cfg.AccountDefaults.PriceFloors.MaxRule) cmpInts(t, "account_defaults.price_floors.max_schema_dims", 3, cfg.AccountDefaults.PriceFloors.MaxSchemaDims) + cmpBools(t, "account_defaults.price_floors.fetch.enabled", false, cfg.AccountDefaults.PriceFloors.Fetcher.Enabled) + cmpStrings(t, "account_defaults.price_floors.fetch.url", "", cfg.AccountDefaults.PriceFloors.Fetcher.URL) + cmpInts(t, "account_defaults.price_floors.fetch.timeout_ms", 3000, cfg.AccountDefaults.PriceFloors.Fetcher.Timeout) + cmpInts(t, "account_defaults.price_floors.fetch.max_file_size_kb", 100, cfg.AccountDefaults.PriceFloors.Fetcher.MaxFileSizeKB) + cmpInts(t, "account_defaults.price_floors.fetch.max_rules", 1000, cfg.AccountDefaults.PriceFloors.Fetcher.MaxRules) + cmpInts(t, "account_defaults.price_floors.fetch.period_sec", 3600, cfg.AccountDefaults.PriceFloors.Fetcher.Period) + cmpInts(t, "account_defaults.price_floors.fetch.max_age_sec", 86400, cfg.AccountDefaults.PriceFloors.Fetcher.MaxAge) + cmpInts(t, "account_defaults.price_floors.fetch.max_schema_dims", 0, cfg.AccountDefaults.PriceFloors.Fetcher.MaxSchemaDims) + cmpStrings(t, "account_defaults.privacy.topicsdomain", "", cfg.AccountDefaults.Privacy.PrivacySandbox.TopicsDomain) + cmpBools(t, "account_defaults.privacy.privacysandbox.cookiedeprecation.enabled", false, cfg.AccountDefaults.Privacy.PrivacySandbox.CookieDeprecation.Enabled) + cmpInts(t, "account_defaults.privacy.privacysandbox.cookiedeprecation.ttl_sec", 604800, cfg.AccountDefaults.Privacy.PrivacySandbox.CookieDeprecation.TTLSec) + cmpBools(t, "account_defaults.events.enabled", false, cfg.AccountDefaults.Events.Enabled) cmpBools(t, "hooks.enabled", false, cfg.Hooks.Enabled) @@ -204,6 +228,14 @@ func TestDefaults(t *testing.T) { cmpInts(t, "account_defaults.privacy.ipv4.anon_keep_bits", 24, cfg.AccountDefaults.Privacy.IPv4Config.AnonKeepBits) //Assert purpose VendorExceptionMap hash tables were built correctly + cmpBools(t, "analytics.agma.enabled", false, cfg.Analytics.Agma.Enabled) + cmpStrings(t, "analytics.agma.endpoint.timeout", "2s", cfg.Analytics.Agma.Endpoint.Timeout) + cmpBools(t, "analytics.agma.endpoint.gzip", false, cfg.Analytics.Agma.Endpoint.Gzip) + cmpStrings(t, "analytics.agma.endppoint.url", "https://go.pbs.agma-analytics.de/v1/prebid-server", cfg.Analytics.Agma.Endpoint.Url) + cmpStrings(t, "analytics.agma.buffers.size", "2MB", cfg.Analytics.Agma.Buffers.BufferSize) + cmpInts(t, "analytics.agma.buffers.count", 100, cfg.Analytics.Agma.Buffers.EventCount) + cmpStrings(t, "analytics.agma.buffers.timeout", "15m", cfg.Analytics.Agma.Buffers.Timeout) + cmpInts(t, "analytics.agma.accounts", 0, len(cfg.Analytics.Agma.Accounts)) expectedTCF2 := TCF2{ Enabled: true, Purpose1: TCF2Purpose{ @@ -211,80 +243,80 @@ func TestDefaults(t *testing.T) { EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true, - VendorExceptions: []openrtb_ext.BidderName{}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + VendorExceptions: []string{}, + VendorExceptionMap: map[string]struct{}{}, }, Purpose2: TCF2Purpose{ EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true, - VendorExceptions: []openrtb_ext.BidderName{}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + VendorExceptions: []string{}, + VendorExceptionMap: map[string]struct{}{}, }, Purpose3: TCF2Purpose{ EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true, - VendorExceptions: []openrtb_ext.BidderName{}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + VendorExceptions: []string{}, + VendorExceptionMap: map[string]struct{}{}, }, Purpose4: TCF2Purpose{ EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true, - VendorExceptions: []openrtb_ext.BidderName{}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + VendorExceptions: []string{}, + VendorExceptionMap: map[string]struct{}{}, }, Purpose5: TCF2Purpose{ EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true, - VendorExceptions: []openrtb_ext.BidderName{}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + VendorExceptions: []string{}, + VendorExceptionMap: map[string]struct{}{}, }, Purpose6: TCF2Purpose{ EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true, - VendorExceptions: []openrtb_ext.BidderName{}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + VendorExceptions: []string{}, + VendorExceptionMap: map[string]struct{}{}, }, Purpose7: TCF2Purpose{ EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true, - VendorExceptions: []openrtb_ext.BidderName{}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + VendorExceptions: []string{}, + VendorExceptionMap: map[string]struct{}{}, }, Purpose8: TCF2Purpose{ EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true, - VendorExceptions: []openrtb_ext.BidderName{}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + VendorExceptions: []string{}, + VendorExceptionMap: map[string]struct{}{}, }, Purpose9: TCF2Purpose{ EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true, - VendorExceptions: []openrtb_ext.BidderName{}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + VendorExceptions: []string{}, + VendorExceptionMap: map[string]struct{}{}, }, Purpose10: TCF2Purpose{ EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: true, - VendorExceptions: []openrtb_ext.BidderName{}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + VendorExceptions: []string{}, + VendorExceptionMap: map[string]struct{}{}, }, SpecialFeature1: TCF2SpecialFeature{ Enforce: true, @@ -369,6 +401,7 @@ external_url: http://prebid-server.prebid.org/ host: prebid-server.prebid.org port: 1234 admin_port: 5678 +stored_requests_timeout_ms: 75 compression: request: enable_gzip: true @@ -415,9 +448,10 @@ metrics: account_debug: false account_stored_responses: false adapter_connections_metrics: true + adapter_buyeruid_scrubbed: false adapter_gdpr_request_blocked: true account_modules_metrics: true -blacklisted_apps: ["spamAppID","sketchy-app-id"] +blocked_apps: ["spamAppID","sketchy-app-id"] account_required: true auto_gen_source_tid: false certificates_file: /etc/ssl/cert.pem @@ -450,6 +484,16 @@ hooks: enabled: true price_floors: enabled: true + fetcher: + worker: 20 + capacity: 20000 + cache_size_mb: 8 + http_client: + max_connections_per_host: 5 + max_idle_connections: 1 + max_idle_connections_per_host: 2 + idle_connection_timeout_seconds: 10 + max_retries: 5 account_defaults: events: enabled: true @@ -461,16 +505,62 @@ account_defaults: use_dynamic_data: true max_rules: 120 max_schema_dims: 5 + fetch: + enabled: true + url: http://test.com/floors + timeout_ms: 500 + max_file_size_kb: 200 + max_rules: 500 + period_sec: 2000 + max_age_sec: 6000 + max_schema_dims: 10 + bidadjustments: + mediatype: + '*': + '*': + '*': + - adjtype: multiplier + value: 1.01 + currency: USD + video-instream: + bidder: + deal_id: + - adjtype: cpm + value: 1.02 + currency: EUR privacy: ipv6: anon_keep_bits: 50 ipv4: anon_keep_bits: 20 + dsa: + default: "{\"dsarequired\":3,\"pubrender\":1,\"datatopub\":2,\"transparency\":[{\"domain\":\"domain.com\",\"dsaparams\":[1]}]}" + gdpr_only: true + privacysandbox: + topicsdomain: "test.com" + cookiedeprecation: + enabled: true + ttl_sec: 86400 tmax_adjustments: enabled: true bidder_response_duration_min_ms: 700 bidder_network_latency_buffer_ms: 100 pbs_response_preparation_duration_ms: 100 +analytics: + agma: + enabled: true + endpoint: + url: "http://test.com" + timeout: "5s" + gzip: false + buffers: + size: 10MB + count: 111 + timeout: 5m + accounts: + - code: agma-code + publisher_id: publisher-id + site_app_id: site-or-app-id `) func cmpStrings(t *testing.T, key, expected, actual string) { @@ -524,6 +614,7 @@ func TestFullConfig(t *testing.T) { cmpInts(t, "garbage_collector_threshold", 1, cfg.GarbageCollectorThreshold) cmpInts(t, "auction_timeouts_ms.default", 50, int(cfg.AuctionTimeouts.Default)) cmpInts(t, "auction_timeouts_ms.max", 123, int(cfg.AuctionTimeouts.Max)) + cmpInts(t, "stored_request_timeout_ms", 75, cfg.StoredRequestsTimeout) cmpStrings(t, "cache.scheme", "http", cfg.CacheURL.Scheme) cmpStrings(t, "cache.host", "prebidcache.net", cfg.CacheURL.Host) cmpStrings(t, "cache.query", "uuid=%PBS_CACHE_UUID%", cfg.CacheURL.Query) @@ -556,6 +647,14 @@ func TestFullConfig(t *testing.T) { //Assert the price floor values cmpBools(t, "price_floors.enabled", true, cfg.PriceFloors.Enabled) + cmpInts(t, "price_floors.fetcher.worker", 20, cfg.PriceFloors.Fetcher.Worker) + cmpInts(t, "price_floors.fetcher.capacity", 20000, cfg.PriceFloors.Fetcher.Capacity) + cmpInts(t, "price_floors.fetcher.cache_size_mb", 8, cfg.PriceFloors.Fetcher.CacheSize) + cmpInts(t, "price_floors.fetcher.http_client.max_connections_per_host", 5, cfg.PriceFloors.Fetcher.HttpClient.MaxConnsPerHost) + cmpInts(t, "price_floors.fetcher.http_client.max_idle_connections", 1, cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConns) + cmpInts(t, "price_floors.fetcher.http_client.max_idle_connections_per_host", 2, cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConnsPerHost) + cmpInts(t, "price_floors.fetcher.http_client.idle_connection_timeout_seconds", 10, cfg.PriceFloors.Fetcher.HttpClient.IdleConnTimeout) + cmpInts(t, "price_floors.fetcher.max_retries", 5, cfg.PriceFloors.Fetcher.MaxRetries) cmpBools(t, "account_defaults.price_floors.enabled", true, cfg.AccountDefaults.PriceFloors.Enabled) cmpInts(t, "account_defaults.price_floors.enforce_floors_rate", 50, cfg.AccountDefaults.PriceFloors.EnforceFloorsRate) cmpBools(t, "account_defaults.price_floors.adjust_for_bid_adjustment", false, cfg.AccountDefaults.PriceFloors.AdjustForBidAdjustment) @@ -563,11 +662,42 @@ func TestFullConfig(t *testing.T) { cmpBools(t, "account_defaults.price_floors.use_dynamic_data", true, cfg.AccountDefaults.PriceFloors.UseDynamicData) cmpInts(t, "account_defaults.price_floors.max_rules", 120, cfg.AccountDefaults.PriceFloors.MaxRule) cmpInts(t, "account_defaults.price_floors.max_schema_dims", 5, cfg.AccountDefaults.PriceFloors.MaxSchemaDims) + cmpBools(t, "account_defaults.price_floors.fetch.enabled", true, cfg.AccountDefaults.PriceFloors.Fetcher.Enabled) + cmpStrings(t, "account_defaults.price_floors.fetch.url", "http://test.com/floors", cfg.AccountDefaults.PriceFloors.Fetcher.URL) + cmpInts(t, "account_defaults.price_floors.fetch.timeout_ms", 500, cfg.AccountDefaults.PriceFloors.Fetcher.Timeout) + cmpInts(t, "account_defaults.price_floors.fetch.max_file_size_kb", 200, cfg.AccountDefaults.PriceFloors.Fetcher.MaxFileSizeKB) + cmpInts(t, "account_defaults.price_floors.fetch.max_rules", 500, cfg.AccountDefaults.PriceFloors.Fetcher.MaxRules) + cmpInts(t, "account_defaults.price_floors.fetch.period_sec", 2000, cfg.AccountDefaults.PriceFloors.Fetcher.Period) + cmpInts(t, "account_defaults.price_floors.fetch.max_age_sec", 6000, cfg.AccountDefaults.PriceFloors.Fetcher.MaxAge) + cmpInts(t, "account_defaults.price_floors.fetch.max_schema_dims", 10, cfg.AccountDefaults.PriceFloors.Fetcher.MaxSchemaDims) + + // Assert the DSA was correctly unmarshalled and DefaultUnpacked was built correctly + expectedDSA := AccountDSA{ + Default: "{\"dsarequired\":3,\"pubrender\":1,\"datatopub\":2,\"transparency\":[{\"domain\":\"domain.com\",\"dsaparams\":[1]}]}", + DefaultUnpacked: &openrtb_ext.ExtRegsDSA{ + Required: ptrutil.ToPtr[int8](3), + PubRender: ptrutil.ToPtr[int8](1), + DataToPub: ptrutil.ToPtr[int8](2), + Transparency: []openrtb_ext.ExtBidDSATransparency{ + { + Domain: "domain.com", + Params: []int{1}, + }, + }, + }, + GDPROnly: true, + } + assert.Equal(t, &expectedDSA, cfg.AccountDefaults.Privacy.DSA) + cmpBools(t, "account_defaults.events.enabled", true, cfg.AccountDefaults.Events.Enabled) cmpInts(t, "account_defaults.privacy.ipv6.anon_keep_bits", 50, cfg.AccountDefaults.Privacy.IPv6Config.AnonKeepBits) cmpInts(t, "account_defaults.privacy.ipv4.anon_keep_bits", 20, cfg.AccountDefaults.Privacy.IPv4Config.AnonKeepBits) + cmpStrings(t, "account_defaults.privacy.topicsdomain", "test.com", cfg.AccountDefaults.Privacy.PrivacySandbox.TopicsDomain) + cmpBools(t, "account_defaults.privacy.cookiedeprecation.enabled", true, cfg.AccountDefaults.Privacy.PrivacySandbox.CookieDeprecation.Enabled) + cmpInts(t, "account_defaults.privacy.cookiedeprecation.ttl_sec", 86400, cfg.AccountDefaults.Privacy.PrivacySandbox.CookieDeprecation.TTLSec) + // Assert compression related defaults cmpBools(t, "compression.request.enable_gzip", true, cfg.Compression.Request.GZIP) cmpBools(t, "compression.response.enable_gzip", false, cfg.Compression.Response.GZIP) @@ -584,12 +714,12 @@ func TestFullConfig(t *testing.T) { cmpBools(t, "lmt.enforce", true, cfg.LMT.Enforce) //Assert the NonStandardPublishers was correctly unmarshalled - cmpStrings(t, "blacklisted_apps", "spamAppID", cfg.BlacklistedApps[0]) - cmpStrings(t, "blacklisted_apps", "sketchy-app-id", cfg.BlacklistedApps[1]) + cmpStrings(t, "blocked_apps", "spamAppID", cfg.BlockedApps[0]) + cmpStrings(t, "blocked_apps", "sketchy-app-id", cfg.BlockedApps[1]) - //Assert the BlacklistedAppMap hash table was built correctly - for i := 0; i < len(cfg.BlacklistedApps); i++ { - cmpBools(t, "cfg.BlacklistedAppMap", true, cfg.BlacklistedAppMap[cfg.BlacklistedApps[i]]) + //Assert the BlockedAppsLookup hash table was built correctly + for i := 0; i < len(cfg.BlockedApps); i++ { + cmpBools(t, "cfg.BlockedAppsLookup", true, cfg.BlockedAppsLookup[cfg.BlockedApps[i]]) } //Assert purpose VendorExceptionMap hash tables were built correctly @@ -600,80 +730,80 @@ func TestFullConfig(t *testing.T) { EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: false, - VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo1a"), openrtb_ext.BidderName("foo1b")}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo1a"): {}, openrtb_ext.BidderName("foo1b"): {}}, + VendorExceptions: []string{"foo1a", "foo1b"}, + VendorExceptionMap: map[string]struct{}{"foo1a": {}, "foo1b": {}}, }, Purpose2: TCF2Purpose{ EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: false, EnforceVendors: false, - VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo2")}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo2"): {}}, + VendorExceptions: []string{"foo2"}, + VendorExceptionMap: map[string]struct{}{"foo2": {}}, }, Purpose3: TCF2Purpose{ EnforceAlgo: TCF2EnforceAlgoBasic, EnforceAlgoID: TCF2BasicEnforcement, EnforcePurpose: true, EnforceVendors: false, - VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo3")}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo3"): {}}, + VendorExceptions: []string{"foo3"}, + VendorExceptionMap: map[string]struct{}{"foo3": {}}, }, Purpose4: TCF2Purpose{ EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: false, - VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo4")}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo4"): {}}, + VendorExceptions: []string{"foo4"}, + VendorExceptionMap: map[string]struct{}{"foo4": {}}, }, Purpose5: TCF2Purpose{ EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: false, - VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo5")}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo5"): {}}, + VendorExceptions: []string{"foo5"}, + VendorExceptionMap: map[string]struct{}{"foo5": {}}, }, Purpose6: TCF2Purpose{ EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: false, - VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo6")}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo6"): {}}, + VendorExceptions: []string{"foo6"}, + VendorExceptionMap: map[string]struct{}{"foo6": {}}, }, Purpose7: TCF2Purpose{ EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: false, - VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo7")}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo7"): {}}, + VendorExceptions: []string{"foo7"}, + VendorExceptionMap: map[string]struct{}{"foo7": {}}, }, Purpose8: TCF2Purpose{ EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: false, - VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo8")}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo8"): {}}, + VendorExceptions: []string{"foo8"}, + VendorExceptionMap: map[string]struct{}{"foo8": {}}, }, Purpose9: TCF2Purpose{ EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: false, - VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo9")}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo9"): {}}, + VendorExceptions: []string{"foo9"}, + VendorExceptionMap: map[string]struct{}{"foo9": {}}, }, Purpose10: TCF2Purpose{ EnforceAlgo: TCF2EnforceAlgoFull, EnforceAlgoID: TCF2FullEnforcement, EnforcePurpose: true, EnforceVendors: false, - VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo10")}, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo10"): {}}, + VendorExceptions: []string{"foo10"}, + VendorExceptionMap: map[string]struct{}{"foo10": {}}, }, SpecialFeature1: TCF2SpecialFeature{ Enforce: true, // true by default @@ -697,7 +827,23 @@ func TestFullConfig(t *testing.T) { 9: &expectedTCF2.Purpose9, 10: &expectedTCF2.Purpose10, } + + expectedBidAdjustments := &openrtb_ext.ExtRequestPrebidBidAdjustments{ + MediaType: openrtb_ext.MediaType{ + WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "*": { + "*": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.01, Currency: "USD"}}, + }, + }, + VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{ + "bidder": { + "deal_id": []openrtb_ext.Adjustment{{Type: "cpm", Value: 1.02, Currency: "EUR"}}, + }, + }, + }, + } assert.Equal(t, expectedTCF2, cfg.GDPR.TCF2, "gdpr.tcf2") + assert.Equal(t, expectedBidAdjustments, cfg.AccountDefaults.BidAdjustments) cmpStrings(t, "currency_converter.fetch_url", "https://currency.prebid.org", cfg.CurrencyConverter.FetchURL) cmpInts(t, "currency_converter.fetch_interval_seconds", 1800, cfg.CurrencyConverter.FetchIntervalSeconds) @@ -717,6 +863,7 @@ func TestFullConfig(t *testing.T) { cmpBools(t, "account_debug", false, cfg.Metrics.Disabled.AccountDebug) cmpBools(t, "account_stored_responses", false, cfg.Metrics.Disabled.AccountStoredResponses) cmpBools(t, "adapter_connections_metrics", true, cfg.Metrics.Disabled.AdapterConnectionMetrics) + cmpBools(t, "adapter_buyeruid_scrubbed", false, cfg.Metrics.Disabled.AdapterBuyerUIDScrubbed) cmpBools(t, "adapter_gdpr_request_blocked", true, cfg.Metrics.Disabled.AdapterGDPRRequestBlocked) cmpStrings(t, "certificates_file", "/etc/ssl/cert.pem", cfg.PemCertsFile) cmpStrings(t, "request_validation.ipv4_private_networks", "1.1.1.0/24", cfg.RequestValidation.IPv4PrivateNetworks[0]) @@ -733,6 +880,16 @@ func TestFullConfig(t *testing.T) { cmpInts(t, "experiment.adscert.remote.signing_timeout_ms", 10, cfg.Experiment.AdCerts.Remote.SigningTimeoutMs) cmpBools(t, "hooks.enabled", true, cfg.Hooks.Enabled) cmpBools(t, "account_modules_metrics", true, cfg.Metrics.Disabled.AccountModulesMetrics) + cmpBools(t, "analytics.agma.enabled", true, cfg.Analytics.Agma.Enabled) + cmpStrings(t, "analytics.agma.endpoint.timeout", "5s", cfg.Analytics.Agma.Endpoint.Timeout) + cmpBools(t, "analytics.agma.endpoint.gzip", false, cfg.Analytics.Agma.Endpoint.Gzip) + cmpStrings(t, "analytics.agma.endpoint.url", "http://test.com", cfg.Analytics.Agma.Endpoint.Url) + cmpStrings(t, "analytics.agma.buffers.size", "10MB", cfg.Analytics.Agma.Buffers.BufferSize) + cmpInts(t, "analytics.agma.buffers.count", 111, cfg.Analytics.Agma.Buffers.EventCount) + cmpStrings(t, "analytics.agma.buffers.timeout", "5m", cfg.Analytics.Agma.Buffers.Timeout) + cmpStrings(t, "analytics.agma.accounts.0.publisher_id", "publisher-id", cfg.Analytics.Agma.Accounts[0].PublisherId) + cmpStrings(t, "analytics.agma.accounts.0.code", "agma-code", cfg.Analytics.Agma.Accounts[0].Code) + cmpStrings(t, "analytics.agma.accounts.0.site_app_id", "site-or-app-id", cfg.Analytics.Agma.Accounts[0].SiteAppId) } func TestValidateConfig(t *testing.T) { @@ -758,6 +915,7 @@ func TestValidateConfig(t *testing.T) { Type: "none", }, }, + StoredRequestsTimeout: 50, StoredVideo: StoredRequests{ Files: FileFetcherConfig{Enabled: true}, InMemoryCache: InMemoryCache{ @@ -771,6 +929,15 @@ func TestValidateConfig(t *testing.T) { Files: FileFetcherConfig{Enabled: true}, InMemoryCache: InMemoryCache{Type: "none"}, }, + AccountDefaults: Account{ + PriceFloors: AccountPriceFloors{ + Fetcher: AccountFloorFetch{ + Timeout: 100, + Period: 300, + MaxAge: 600, + }, + }, + }, } v := viper.New() @@ -837,7 +1004,6 @@ func TestUserSyncFromEnv(t *testing.T) { assert.Equal(t, "http://somedifferent.url/sync?redirect={{.RedirectURL}}", cfg.BidderInfos["bidder2"].Syncer.IFrame.URL) assert.Nil(t, cfg.BidderInfos["bidder2"].Syncer.Redirect) assert.Nil(t, cfg.BidderInfos["bidder2"].Syncer.SupportCORS) - } func TestBidderInfoFromEnv(t *testing.T) { @@ -1011,6 +1177,16 @@ func TestIsConfigInfoPresent(t *testing.T) { } } +func TestNegativeOrZeroStoredRequestsTimeout(t *testing.T) { + cfg, v := newDefaultConfig(t) + + cfg.StoredRequestsTimeout = -1 + assertOneError(t, cfg.validate(v), "cfg.stored_requests_timeout_ms must be > 0. Got -1") + + cfg.StoredRequestsTimeout = 0 + assertOneError(t, cfg.validate(v), "cfg.stored_requests_timeout_ms must be > 0. Got 0") +} + func TestNegativeRequestSize(t *testing.T) { cfg, v := newDefaultConfig(t) cfg.MaxRequestSize = -1 @@ -1294,6 +1470,155 @@ func TestSpecialFeature1VendorExceptionMap(t *testing.T) { } } +func TestSetConfigBidderInfoNillableFields(t *testing.T) { + falseValue := false + trueValue := true + + bidder1ConfigFalses := []byte(` + adapters: + bidder1: + disabled: false + modifyingVastXmlAllowed: false`) + bidder1ConfigTrues := []byte(` + adapters: + bidder1: + disabled: true + modifyingVastXmlAllowed: true`) + bidder1ConfigNils := []byte(` + adapters: + bidder1: + disabled: null + modifyingVastXmlAllowed: null`) + bidder1Bidder2ConfigMixed := []byte(` + adapters: + bidder1: + disabled: true + modifyingVastXmlAllowed: false + bidder2: + disabled: false + modifyingVastXmlAllowed: true`) + + tests := []struct { + name string + rawConfig []byte + bidderInfos BidderInfos + expected nillableFieldBidderInfos + expectError bool + }{ + { + name: "viper and bidder infos are nil", + expected: nil, + }, + { + name: "viper is nil", + bidderInfos: map[string]BidderInfo{}, + expected: nil, + }, + { + name: "bidder infos is nil", + rawConfig: []byte{}, + expected: nil, + }, + { + name: "bidder infos is empty", + bidderInfos: map[string]BidderInfo{}, + expected: nil, + }, + { + name: "one: bidder info has nillable fields as false, viper has as nil", + bidderInfos: map[string]BidderInfo{ + "bidder1": {Disabled: false, ModifyingVastXmlAllowed: false}, + }, + rawConfig: bidder1ConfigNils, + expected: nillableFieldBidderInfos{ + "bidder1": nillableFieldBidderInfo{ + nillableFields: bidderInfoNillableFields{ + Disabled: nil, + ModifyingVastXmlAllowed: nil, + }, + bidderInfo: BidderInfo{Disabled: false, ModifyingVastXmlAllowed: false}, + }, + }, + }, + { + name: "one: bidder info has nillable fields as false, viper has as false", + bidderInfos: map[string]BidderInfo{ + "bidder1": {Disabled: false, ModifyingVastXmlAllowed: false}, + }, + rawConfig: bidder1ConfigFalses, + expected: nillableFieldBidderInfos{ + "bidder1": nillableFieldBidderInfo{ + nillableFields: bidderInfoNillableFields{ + Disabled: &falseValue, + ModifyingVastXmlAllowed: &falseValue, + }, + bidderInfo: BidderInfo{Disabled: false, ModifyingVastXmlAllowed: false}, + }, + }, + }, + { + name: "one: bidder info has nillable fields as false, viper has as true", + bidderInfos: map[string]BidderInfo{ + "bidder1": {Disabled: false, ModifyingVastXmlAllowed: false}, + }, + rawConfig: bidder1ConfigTrues, + expected: nillableFieldBidderInfos{ + "bidder1": nillableFieldBidderInfo{ + nillableFields: bidderInfoNillableFields{ + Disabled: &trueValue, + ModifyingVastXmlAllowed: &trueValue, + }, + bidderInfo: BidderInfo{Disabled: false, ModifyingVastXmlAllowed: false}, + }, + }, + }, + { + name: "many with extra info: bidder infos have nillable fields as false and true, viper has as true and false", + bidderInfos: map[string]BidderInfo{ + "bidder1": {Disabled: false, ModifyingVastXmlAllowed: true, Endpoint: "endpoint a"}, + "bidder2": {Disabled: true, ModifyingVastXmlAllowed: false, Endpoint: "endpoint b"}, + }, + rawConfig: bidder1Bidder2ConfigMixed, + expected: nillableFieldBidderInfos{ + "bidder1": nillableFieldBidderInfo{ + nillableFields: bidderInfoNillableFields{ + Disabled: &trueValue, + ModifyingVastXmlAllowed: &falseValue, + }, + bidderInfo: BidderInfo{Disabled: false, ModifyingVastXmlAllowed: true, Endpoint: "endpoint a"}, + }, + "bidder2": nillableFieldBidderInfo{ + nillableFields: bidderInfoNillableFields{ + Disabled: &falseValue, + ModifyingVastXmlAllowed: &trueValue, + }, + bidderInfo: BidderInfo{Disabled: true, ModifyingVastXmlAllowed: false, Endpoint: "endpoint b"}, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := viper.New() + v.SetConfigType("yaml") + for bidderName := range tt.bidderInfos { + setBidderDefaults(v, strings.ToLower(bidderName)) + } + v.ReadConfig(bytes.NewBuffer(tt.rawConfig)) + + result, err := setConfigBidderInfoNillableFields(v, tt.bidderInfos) + + assert.Equal(t, tt.expected, result) + if tt.expectError { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + } + }) + } +} + func TestTCF2PurposeEnforced(t *testing.T) { tests := []struct { description string @@ -1463,40 +1788,40 @@ func TestTCF2PurposeVendorExceptions(t *testing.T) { tests := []struct { description string givePurposeConfigNil bool - givePurpose1ExceptionMap map[openrtb_ext.BidderName]struct{} - givePurpose2ExceptionMap map[openrtb_ext.BidderName]struct{} + givePurpose1ExceptionMap map[string]struct{} + givePurpose2ExceptionMap map[string]struct{} givePurpose consentconstants.Purpose - wantExceptionMap map[openrtb_ext.BidderName]struct{} + wantExceptionMap map[string]struct{} }{ { description: "Purpose config is nil", givePurposeConfigNil: true, givePurpose: 1, - wantExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + wantExceptionMap: map[string]struct{}{}, }, { description: "Nil - exception map not defined for purpose", givePurpose: 1, - wantExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + wantExceptionMap: map[string]struct{}{}, }, { description: "Empty - exception map empty for purpose", givePurpose: 1, - givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{}, - wantExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + givePurpose1ExceptionMap: map[string]struct{}{}, + wantExceptionMap: map[string]struct{}{}, }, { description: "Nonempty - exception map with multiple entries for purpose", givePurpose: 1, - givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, - wantExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, + givePurpose1ExceptionMap: map[string]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, + wantExceptionMap: map[string]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, }, { description: "Nonempty - exception map with multiple entries for different purpose", givePurpose: 2, - givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, - givePurpose2ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}}, - wantExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}}, + givePurpose1ExceptionMap: map[string]struct{}{"rubicon": {}, "appnexus": {}, "index": {}}, + givePurpose2ExceptionMap: map[string]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}}, + wantExceptionMap: map[string]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}}, }, } @@ -1570,3 +1895,70 @@ func TestTCF2FeatureOneVendorException(t *testing.T) { assert.Equal(t, tt.wantIsVendorException, value, tt.description) } } + +func TestUnpackDSADefault(t *testing.T) { + tests := []struct { + name string + giveDSA *AccountDSA + wantError bool + }{ + { + name: "nil", + giveDSA: nil, + wantError: false, + }, + { + name: "empty", + giveDSA: &AccountDSA{ + Default: "", + }, + wantError: false, + }, + { + name: "empty_json", + giveDSA: &AccountDSA{ + Default: "{}", + }, + wantError: false, + }, + { + name: "well_formed", + giveDSA: &AccountDSA{ + Default: "{\"dsarequired\":3,\"pubrender\":1,\"datatopub\":2,\"transparency\":[{\"domain\":\"domain.com\",\"dsaparams\":[1]}]}", + }, + wantError: false, + }, + { + name: "well_formed_with_extra_fields", + giveDSA: &AccountDSA{ + Default: "{\"unmappedkey\":\"unmappedvalue\",\"dsarequired\":3,\"pubrender\":1,\"datatopub\":2,\"transparency\":[{\"domain\":\"domain.com\",\"dsaparams\":[1]}]}", + }, + wantError: false, + }, + { + name: "invalid_type", + giveDSA: &AccountDSA{ + Default: "{\"dsarequired\":\"invalid\",\"pubrender\":1,\"datatopub\":2,\"transparency\":[{\"domain\":\"domain.com\",\"dsaparams\":[1]}]}", + }, + wantError: true, + }, + { + name: "invalid_malformed_missing_colon", + giveDSA: &AccountDSA{ + Default: "{\"dsarequired\"3,\"pubrender\":1,\"datatopub\":2,\"transparency\":[{\"domain\":\"domain.com\",\"dsaparams\":[1]}]}", + }, + wantError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := UnpackDSADefault(tt.giveDSA) + if tt.wantError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/config/events.go b/config/events.go index cf3139d83ca..c73460e0491 100644 --- a/config/events.go +++ b/config/events.go @@ -82,11 +82,9 @@ func (e Events) validate(errs []error) []error { // validateVASTEvents verifies the all VASTEvent objects and returns error if at least one is invalid. func validateVASTEvents(events []VASTEvent) error { - if events != nil { - for i, event := range events { - if err := event.validate(); err != nil { - return fmt.Errorf(err.Error(), i, i) - } + for i, event := range events { + if err := event.validate(); err != nil { + return fmt.Errorf(err.Error(), i, i) } } return nil diff --git a/currency/currency.go b/currency/currency.go new file mode 100644 index 00000000000..b4d886274e3 --- /dev/null +++ b/currency/currency.go @@ -0,0 +1,42 @@ +package currency + +import ( + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +func GetAuctionCurrencyRates(currencyConverter *RateConverter, requestRates *openrtb_ext.ExtRequestCurrency) Conversions { + if currencyConverter == nil && requestRates == nil { + return nil + } + + if requestRates == nil { + // No bidRequest.ext.currency field was found, use PBS rates as usual + return currencyConverter.Rates() + } + + // currencyConverter will never be nil, refer main.serve(), adding this check for future usecases + if currencyConverter == nil { + return NewRates(requestRates.ConversionRates) + } + + // If bidRequest.ext.currency.usepbsrates is nil, we understand its value as true. It will be false + // only if it's explicitly set to false + usePbsRates := requestRates.UsePBSRates == nil || *requestRates.UsePBSRates + + if !usePbsRates { + // At this point, we can safely assume the ConversionRates map is not empty because + // validateCustomRates(bidReqCurrencyRates *openrtb_ext.ExtRequestCurrency) would have + // thrown an error under such conditions. + return NewRates(requestRates.ConversionRates) + } + + // Both PBS and custom rates can be used, check if ConversionRates is not empty + if len(requestRates.ConversionRates) == 0 { + // Custom rates map is empty, use PBS rates only + return currencyConverter.Rates() + } + + // Return an AggregateConversions object that includes both custom and PBS currency rates but will + // prioritize custom rates over PBS rates whenever a currency rate is found in both + return NewAggregateConversions(NewRates(requestRates.ConversionRates), currencyConverter.Rates()) +} diff --git a/currency/currency_mock.go b/currency/currency_mock.go new file mode 100644 index 00000000000..f25974380c7 --- /dev/null +++ b/currency/currency_mock.go @@ -0,0 +1,20 @@ +package currency + +import ( + "io" + "net/http" + "strings" +) + +// MockCurrencyRatesHttpClient is a simple http client mock returning a constant response body +type MockCurrencyRatesHttpClient struct { + ResponseBody string +} + +func (m *MockCurrencyRatesHttpClient) Do(req *http.Request) (*http.Response, error) { + return &http.Response{ + Status: "200 OK", + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(m.ResponseBody)), + }, nil +} diff --git a/currency/currency_test.go b/currency/currency_test.go new file mode 100644 index 00000000000..e86ee4537fe --- /dev/null +++ b/currency/currency_test.go @@ -0,0 +1,199 @@ +package currency + +import ( + "testing" + "time" + + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +func TestGetAuctionCurrencyRates(t *testing.T) { + pbsRates := map[string]map[string]float64{ + "MXN": { + "USD": 20.13, + "EUR": 27.82, + "JPY": 5.09, // "MXN" to "JPY" rate not found in customRates + }, + } + + customRates := map[string]map[string]float64{ + "MXN": { + "USD": 25.00, // different rate than in pbsRates + "EUR": 27.82, // same as in pbsRates + "GBP": 31.12, // not found in pbsRates at all + }, + } + + expectedRateEngineRates := map[string]map[string]float64{ + "MXN": { + "USD": 25.00, // rates engine will prioritize the value found in custom rates + "EUR": 27.82, // same value in both the engine reads the custom entry first + "JPY": 5.09, // the engine will find it in the pbsRates conversions + "GBP": 31.12, // the engine will find it in the custom conversions + }, + } + + setupMockRateConverter := func(pbsRates map[string]map[string]float64) *RateConverter { + if pbsRates == nil { + return nil + } + + jsonPbsRates, err := jsonutil.Marshal(pbsRates) + if err != nil { + t.Fatalf("Failed to marshal PBS rates: %v", err) + } + + // Init mock currency conversion service + mockCurrencyClient := &MockCurrencyRatesHttpClient{ + ResponseBody: `{"dataAsOf":"2018-09-12","conversions":` + string(jsonPbsRates) + `}`, + } + + return NewRateConverter( + mockCurrencyClient, + "currency.fake.com", + 24*time.Hour, + ) + } + + type args struct { + currencyConverter *RateConverter + requestRates *openrtb_ext.ExtRequestCurrency + } + tests := []struct { + name string + args args + assertRates map[string]map[string]float64 + }{ + { + name: "valid ConversionRates, valid pbsRates, false UsePBSRates. Resulting rates identical to customRates", + args: args{ + currencyConverter: setupMockRateConverter(pbsRates), + requestRates: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: ptrutil.ToPtr(false), + }, + }, + assertRates: customRates, + }, + { + name: "valid ConversionRates, valid pbsRates, true UsePBSRates. Resulting rates are a mix but customRates gets priority", + args: args{ + currencyConverter: setupMockRateConverter(pbsRates), + requestRates: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: ptrutil.ToPtr(true), + }, + }, + assertRates: expectedRateEngineRates, + }, + { + name: "valid ConversionRates, nil pbsRates, false UsePBSRates. Resulting rates identical to customRates", + args: args{ + currencyConverter: nil, + requestRates: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: ptrutil.ToPtr(false), + }, + }, + assertRates: customRates, + }, + { + name: "valid ConversionRates, nil pbsRates, true UsePBSRates. Resulting rates identical to customRates", + args: args{ + currencyConverter: nil, + requestRates: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: ptrutil.ToPtr(true), + }, + }, + assertRates: customRates, + }, + { + name: "empty ConversionRates, valid pbsRates, false UsePBSRates. Because pbsRates cannot be used, disable currency conversion", + args: args{ + currencyConverter: setupMockRateConverter(pbsRates), + requestRates: &openrtb_ext.ExtRequestCurrency{ + // ConversionRates inCustomRates not initialized makes for a zero-length map + UsePBSRates: ptrutil.ToPtr(false), + }, + }, + assertRates: nil, + }, + { + name: "nil ConversionRates, valid pbsRates, true UsePBSRates. Resulting rates will be identical to pbsRates", + args: args{ + currencyConverter: setupMockRateConverter(pbsRates), + requestRates: nil, + }, + assertRates: pbsRates, + }, + { + name: "empty ConversionRates, nil pbsRates, false UsePBSRates. No conversion rates available, disable currency conversion", + args: args{ + currencyConverter: setupMockRateConverter(pbsRates), + requestRates: &openrtb_ext.ExtRequestCurrency{ + // ConversionRates inCustomRates not initialized makes for a zero-length map + UsePBSRates: ptrutil.ToPtr(false), + }, + }, + assertRates: nil, + }, + + { + name: "empty ConversionRates, nil pbsRates, true UsePBSRates. No conversion rates available, disable currency conversion", + args: args{ + currencyConverter: nil, + requestRates: &openrtb_ext.ExtRequestCurrency{ + // ConversionRates inCustomRates not initialized makes for a zero-length map + UsePBSRates: ptrutil.ToPtr(true), + }, + }, + assertRates: nil, + }, + { + name: "nil customRates, nil pbsRates. No conversion rates available, disable currency conversion", + args: args{ + currencyConverter: nil, + requestRates: nil, + }, + assertRates: nil, + }, + { + name: "empty ConversionRates, valid pbsRates, true UsePBSRates. Resulting rates will be identical to pbsRates", + args: args{ + currencyConverter: setupMockRateConverter(pbsRates), + requestRates: &openrtb_ext.ExtRequestCurrency{ + // ConversionRates inCustomRates not initialized makes for a zero-length map + UsePBSRates: ptrutil.ToPtr(true), + }, + }, + assertRates: pbsRates, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.args.currencyConverter != nil { + tt.args.currencyConverter.Run() + } + auctionRates := GetAuctionCurrencyRates(tt.args.currencyConverter, tt.args.requestRates) + if tt.args.currencyConverter == nil && tt.args.requestRates == nil && tt.assertRates == nil { + assert.Nil(t, auctionRates) + } else if tt.assertRates == nil { + rate, err := auctionRates.GetRate("USD", "MXN") + assert.Error(t, err, tt.name) + assert.Equal(t, float64(0), rate, tt.name) + } else { + for fromCurrency, rates := range tt.assertRates { + for toCurrency, expectedRate := range rates { + actualRate, err := auctionRates.GetRate(fromCurrency, toCurrency) + assert.NoError(t, err, tt.name) + assert.Equal(t, expectedRate, actualRate, tt.name) + } + } + } + }) + } +} diff --git a/currency/rate_converter.go b/currency/rate_converter.go index cda8d763048..2e39b57f403 100644 --- a/currency/rate_converter.go +++ b/currency/rate_converter.go @@ -8,9 +8,9 @@ import ( "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/timeutil" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/timeutil" ) // RateConverter holds the currencies conversion rates dictionary @@ -138,8 +138,7 @@ func (rc *RateConverter) checkStaleRates() bool { // GetInfo returns setup information about the converter func (rc *RateConverter) GetInfo() ConverterInfo { - var rates *map[string]map[string]float64 - rates = rc.Rates().GetRates() + var rates *map[string]map[string]float64 = rc.Rates().GetRates() return converterInfo{ source: rc.syncSourceURL, lastUpdated: rc.LastUpdated(), diff --git a/currency/rate_converter_test.go b/currency/rate_converter_test.go index 617aa02e96a..95049916194 100644 --- a/currency/rate_converter_test.go +++ b/currency/rate_converter_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/util/task" + "github.com/prebid/prebid-server/v3/util/task" "github.com/stretchr/testify/assert" ) diff --git a/currency/rates_test.go b/currency/rates_test.go index 86c25d14ac0..6fb444f32c4 100644 --- a/currency/rates_test.go +++ b/currency/rates_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) func TestUnMarshallRates(t *testing.T) { diff --git a/currency/validation.go b/currency/validation.go index 7a0e2aa02bd..5e23362e5b1 100644 --- a/currency/validation.go +++ b/currency/validation.go @@ -5,8 +5,8 @@ import ( "golang.org/x/text/currency" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // ValidateCustomRates throws a bad input error if any of the 3-digit currency codes found in diff --git a/currency/validation_test.go b/currency/validation_test.go index d49b9824986..0edd5c89a34 100644 --- a/currency/validation_test.go +++ b/currency/validation_test.go @@ -3,8 +3,8 @@ package currency import ( "testing" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/docs/build/README.md b/docs/build/README.md new file mode 100644 index 00000000000..0b52671f216 --- /dev/null +++ b/docs/build/README.md @@ -0,0 +1,110 @@ +## Overview + +As of v2.31.0, Prebid Server contains a module that requires CGo which introduces both build and runtime dependencies. To build, you need a C compiler, preferably gcc. To run, you may require one or more runtime dependencies, most notably libatomic. + +## Examples +For a containerized example, see the Dockerfile. +For manual build examples, including some cross-compilation use cases, see below. + +### From darwin amd64 + +#### To darwin amd64 +`GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 go build` + +Running the built binary on mac amd64: +`./prebid-server --stderrthreshold=WARNING -v=2` + +#### To darwin arm64 +`GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build` + +Running the built binary on mac arm64: +`./prebid-server --stderrthreshold=WARNING -v=2` + +#### To windows amd64 +Build +Install mingw-w64 which consists of a gcc compiler port you can use to generate windows binaries: +`brew install mingw-w64` + +`GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC="x86_64-w64-mingw32-gcc" go build` + +Run +Running the built binary on windows: +`.\prebid-server.exe --sderrthreshold=WARNING =v=2` + +You may receive the following errors or something similar: +``` +"The code execution cannot proceed because libatomic-1.dll was not found." +"The code execution cannot proceed because libwinpthread-1.dll was not found." +``` + +To resolve these errors, copy the following files from mingw-64 on your mac to `C:/windows/System32` and re-run: +`/usr/local/Cellar/mingw-w64/12.0.0_1/toolchain-x86_64/x86_64-w64-mingw32/lib/libatomic-1.dll` +`/usr/local/Cellar/mingw-w64/12.0.0_1/toolchain-x86_64/x86_64-w64-mingw32/bin/libwinpthread-1.dll` + +### From windows amd64 +#### To windows amd64 +Build +`set CGO_ENABLED=1` +`set GOOS=windows` +`set GOARCH=amd64` +`go build . && .\prebid-server.exe --stderrthreshold=WARNING -v=2` + +You may receive the following error or something similar: +``` +# runtime/cgo +cgo: C compiler "gcc" not found: exec: "gcc": executable file not found in %PATH% +``` + +To resolve the error, install MSYS2: +1) Download the installer (https://www.msys2.org/) +2) Run the installer and follow the steps of the installation wizard +3) Run MSYS2 which will open an MSYS2 terminal for you +4) In the MSYS2 terminal, install windows/amd64 gcc toolchain: `pacman -S --needed base-devel mingw-w64-x86_64-gcc` +5) Enter `Y` when prompted whether to proceed with the installation +6) Add the path of your MinGW-w64 `bin` folder to the Windows `PATH` environment variable by using the following steps: + - In the Windows search bar, type Settings to open your Windows Settings. + - Search for Edit environment variables for your account. + - In your User variables, select the `Path` variable and then select Edit. + - Select New and add the MinGW-w64 destination folder you recorded during the installation process to the list. If you used the default settings above, then this will be the path: `C:\msys64\ucrt64\bin`. + - Select OK, and then select OK again in the Environment Variables window to update the `PATH` environment variable. You have to reopen any console windows for the updated `PATH` environment variable to be available. +7) Confirm gcc installed: `gcc --version` + +Run +Running the built binary on windows: +`go build . && .\prebid-server.exe --stderrthreshold=WARNING -v=2` + +You may receive the following errors or something similar: +``` +"The code execution cannot proceed because libatomic-1.dll was not found." +"The code execution cannot proceed because libwinpthread-1.dll was not found." +``` +To resolve these errors, copy the following files from MSYS2 installation to `C:/windows/System32` and re-run: +`C:\mysys64\mingw64\bin\libatomic-1.dll` +`C:\mysys64\mingw64\bin\libwinpthread-1.dll` + +### From linux amd64 +#### To linux amd64 +Note +These instructions are for building and running on Debian-based distributions + +Build +`GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build` + +You may receive the following error or something similar: +``` +# runtime/cgo +cgo: C compiler "gcc" not found: exec: "gcc": executable file not found in $PATH +``` +To resolve the error, install gcc and re-build: +`sudo apt-get install -y gcc` + +Run +Running the built binary on Linux: +`./prebid-server --stderrthreshold=WARNING -v=2` + +You may receive the following error or something similar: +``` +... error while loading shared libraries: libatomic.so.1: cannot open shared object file ... +``` +To resolve the error, install libatomic1 and re-run: +`sudo apt-get install -y libatomic1` \ No newline at end of file diff --git a/docs/developers/configuration.md b/docs/developers/configuration.md index 9a22fd1ac7f..39789d49541 100644 --- a/docs/developers/configuration.md +++ b/docs/developers/configuration.md @@ -1,14 +1,160 @@ # Configuration -Configuration is handled by [Viper](https://github.com/spf13/viper), which supports [many ways](https://github.com/spf13/viper#why-viper) of setting config values. +Prebid Server is configured using environment variables, a `pbs.json` file, or a `pbs.yaml` file, in that order of precedence. Configuration files are read from either the application directory or `/etc/config`. -As a general rule, Prebid Server will log its resolved config values on startup and exit immediately if they're not valid. +Upon starting, Prebid Server logs the resolved configuration to standard out with passwords and secrets redacted. If there's an error with the configuration, the application will log the error and exit. -For development, it's easiest to define your config inside a `pbs.yaml` file in the project root. +# Sections +> [!IMPORTANT] +> As we are still developing this guide, please refer to the [configuration structures in code](../../config/config.go) for a complete definition of the options. -## Available options +- [General](#general) +- [Privacy](#privacy) + - [GDPR](#gdpr) -For now, see [the contract classes](../../config/config.go) in the code. -Also note that `Viper` will also read environment variables for config values. Prebid Server will look for the prefix `PBS_` on the environment variables, and map underscores (`_`) -to periods. For example, to set `host_cookie.ttl_days` via an environment variable, set `PBS_HOST_COOKIE_TTL_DAYS` to the desired value. +# General + +### `external_url` +String value that specifies the external url to reach your Prebid Server instance. It's used for event tracking and user sync callbacks, and is shared with bidders in outgoing requests at `req.ext.prebid.server.externalurl`. Defaults to empty. + +
+ Example +

+ + JSON: + ``` + { + "external_url": "https://your-pbs-server.com" + } + ``` + + YAML: + ``` + external_url: https://your-pbs-server.com + ``` + + Environment Variable: + ``` + PBS_EXTERNAL_URL: https://your-pbs-server.com + ``` + +

+
+ +### `host` +String value that specifies the address the server will listen to for connections. If the value is empty, Prebid Server will listen on all available addresses, which is a common configuration. This value is also used for the Prometheus endpoint, if enabled. Defaults to empty. + +
+ Example +

+ + JSON: + ``` + { + "host": "127.0.0.1" + } + ``` + + YAML: + ``` + host: 127.0.0.1 + ``` + + Environment Variable: + ``` + PBS_HOST: 127.0.0.1 + ``` + +

+
+ +### `port` +Integer value that specifies the port the server will listen to for connections. Defaults to `8000`. + +
+ Example +

+ + JSON: + ``` + { + "port": 8000 + } + ``` + + YAML: + ``` + port: 8000 + ``` + + Environment Variable: + ``` + PBS_PORT: 8000 + ``` + +

+
+ +# Privacy + +## GDPR + +### `gdpr.enabled` +Boolean value that determines if GDPR processing for TCF signals is enabled. Defaults to `true`. +
+ Example +

+ + JSON: + ``` + { + "gdpr": { + "enabled": true + } + } + ``` + + YAML: + ``` + gdpr: + enabled: true + ``` + + Environment Variable: + ``` + PBS_GDPR_ENABLED: true + ``` + +

+
+ + +### `gdpr.default_value` (required) +String value that determines whether GDPR is enabled when no regulatory signal is available in the request. A value of `"0"` disables it by default and a value of `"1"` enables it. This is a required configuration value with no default. +
+ Example +

+ + JSON: + ``` + { + "gdpr": { + "default_value": "0" + } + } + ``` + + YAML: + ``` + gdpr: + default_value: "0" + ``` + + Environment Variable: + ``` + PBS_GDPR_DEFAULT_VALUE: 0 + ``` + +

+
diff --git a/docs/developers/features.md b/docs/developers/features.md deleted file mode 100644 index b9bb9053ed5..00000000000 --- a/docs/developers/features.md +++ /dev/null @@ -1,12 +0,0 @@ -# Features - -Prebid Server documentation has been moved to the prebid.org website: - -- [Adding a new bidder](https://docs.prebid.org/prebid-server/developers/add-new-bidder-go.html) -- [Adding a new analytics module](https://docs.prebid.org/prebid-server/developers/pbs-build-an-analytics-adapter.html) -- [Currency](https://docs.prebid.org/prebid-server/features/pbs-currency.html) -- [Prebid Server and GDPR](https://docs.google.com/document/d/1g0zAYc_EfqyilKD8N2qQ47uz0hdahY-t8vfb-vxZL5w/edit#heading=h.8zebax5ncz0t) -- [Prebid and TCF2](https://docs.google.com/document/d/1fBRaodKifv1pYsWY3ia-9K96VHUjd8kKvxZlOsozm8E/edit#heading=h.hlpacpauqwkx) -- [Prebid Server User ID Sync](https://docs.prebid.org/prebid-server/developers/pbs-cookie-sync.html) -- [Cookie Sync](https://docs.prebid.org/prebid-server/developers/pbs-cookie-sync.html) -- [Default Request](https://docs.prebid.org/prebid-server/features/pbs-default-request.html) diff --git a/docs/endpoints.md b/docs/endpoints.md deleted file mode 100644 index 88116144a41..00000000000 --- a/docs/endpoints.md +++ /dev/null @@ -1 +0,0 @@ -Endpoint documentation has been moved to prebid.org: [https://docs.prebid.org/prebid-server/endpoints/pbs-endpoint-overview.html](https://docs.prebid.org/prebid-server/endpoints/pbs-endpoint-overview.html) diff --git a/dsa/validate.go b/dsa/validate.go new file mode 100644 index 00000000000..3dce2fba1c1 --- /dev/null +++ b/dsa/validate.go @@ -0,0 +1,103 @@ +package dsa + +import ( + "errors" + + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +// Required values representing whether a DSA object is required +const ( + Required int8 = 2 // bid responses without DSA object will not be accepted + RequiredOnlinePlatform int8 = 3 // bid responses without DSA object will not be accepted, Publisher is Online Platform +) + +// PubRender values representing publisher rendering intentions +const ( + PubRenderCannotRender int8 = 0 // publisher can't render + PubRenderWillRender int8 = 2 // publisher will render +) + +// AdRender values representing buyer/advertiser rendering intentions +const ( + AdRenderWillRender int8 = 1 // buyer/advertiser will render +) + +var ( + ErrDsaMissing = errors.New("DSA object missing when required") + ErrBehalfTooLong = errors.New("DSA behalf exceeds limit of 100 chars") + ErrPaidTooLong = errors.New("DSA paid exceeds limit of 100 chars") + ErrNeitherWillRender = errors.New("DSA publisher and buyer both signal will not render") + ErrBothWillRender = errors.New("DSA publisher and buyer both signal will render") +) + +const ( + behalfMaxLength = 100 + paidMaxLength = 100 +) + +// Validate determines whether a given bid is valid from a DSA perspective. +// A bid is considered valid unless the bid request indicates that a DSA object is required +// in bid responses and the object happens to be missing from the specified bid, or if the bid +// DSA object contents are invalid +func Validate(req *openrtb_ext.RequestWrapper, bid *entities.PbsOrtbBid) error { + reqDSA := getReqDSA(req) + bidDSA := getBidDSA(bid) + + if dsaRequired(reqDSA) && bidDSA == nil { + return ErrDsaMissing + } + if bidDSA == nil { + return nil + } + if len(bidDSA.Behalf) > behalfMaxLength { + return ErrBehalfTooLong + } + if len(bidDSA.Paid) > paidMaxLength { + return ErrPaidTooLong + } + if reqDSA != nil && reqDSA.PubRender != nil && bidDSA.AdRender != nil { + if *reqDSA.PubRender == PubRenderCannotRender && *bidDSA.AdRender != AdRenderWillRender { + return ErrNeitherWillRender + } + if *reqDSA.PubRender == PubRenderWillRender && *bidDSA.AdRender == AdRenderWillRender { + return ErrBothWillRender + } + } + return nil +} + +// dsaRequired examines the bid request to determine if the dsarequired field indicates +// that bid responses include a dsa object +func dsaRequired(dsa *openrtb_ext.ExtRegsDSA) bool { + if dsa == nil || dsa.Required == nil { + return false + } + return *dsa.Required == Required || *dsa.Required == RequiredOnlinePlatform +} + +// getReqDSA retrieves the DSA object from the request +func getReqDSA(req *openrtb_ext.RequestWrapper) *openrtb_ext.ExtRegsDSA { + if req == nil { + return nil + } + regExt, err := req.GetRegExt() + if regExt == nil || err != nil { + return nil + } + return regExt.GetDSA() +} + +// getBidDSA retrieves the DSA object from the bid +func getBidDSA(bid *entities.PbsOrtbBid) *openrtb_ext.ExtBidDSA { + if bid == nil || bid.Bid == nil { + return nil + } + var bidExt openrtb_ext.ExtBid + if err := jsonutil.Unmarshal(bid.Bid.Ext, &bidExt); err != nil { + return nil + } + return bidExt.DSA +} diff --git a/dsa/validate_test.go b/dsa/validate_test.go new file mode 100644 index 00000000000..e0af9bc80af --- /dev/null +++ b/dsa/validate_test.go @@ -0,0 +1,396 @@ +package dsa + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +func TestValidate(t *testing.T) { + var ( + validBehalf = strings.Repeat("a", 100) + invalidBehalf = strings.Repeat("a", 101) + validPaid = strings.Repeat("a", 100) + invalidPaid = strings.Repeat("a", 101) + ) + + tests := []struct { + name string + giveRequest *openrtb_ext.RequestWrapper + giveBid *entities.PbsOrtbBid + wantError error + }{ + { + name: "nil", + giveRequest: nil, + giveBid: nil, + wantError: nil, + }, + { + name: "request_nil", + giveRequest: nil, + giveBid: &entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"dsa":{"behalf":"` + validBehalf + `","paid":"` + validPaid + `","adrender":1}}`), + }, + }, + wantError: nil, + }, + { + name: "not_required_and_bid_is_nil", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: json.RawMessage(`{"dsa": {"dsarequired": 0}}`), + }, + }, + }, + giveBid: nil, + wantError: nil, + }, + { + name: "not_required_and_bid_dsa_is_valid", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: json.RawMessage(`{"dsa": {"dsarequired": 0,"pubrender":0}}`), + }, + }, + }, + giveBid: &entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"dsa":{"behalf":"` + validBehalf + `","paid":"` + validPaid + `","adrender":1}}`), + }, + }, + wantError: nil, + }, + { + name: "required_and_bid_is_nil", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: json.RawMessage(`{"dsa": {"dsarequired": 2}}`), + }, + }, + }, + giveBid: nil, + wantError: ErrDsaMissing, + }, + { + name: "required_and_bid_dsa_has_invalid_behalf", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: json.RawMessage(`{"dsa": {"dsarequired": 2}}`), + }, + }, + }, + giveBid: &entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"dsa":{"behalf":"` + invalidBehalf + `"}}`), + }, + }, + wantError: ErrBehalfTooLong, + }, + { + name: "required_and_bid_dsa_has_invalid_paid", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: json.RawMessage(`{"dsa": {"dsarequired": 2}}`), + }, + }, + }, + giveBid: &entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"dsa":{"paid":"` + invalidPaid + `"}}`), + }, + }, + wantError: ErrPaidTooLong, + }, + { + name: "required_and_neither_will_render", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: json.RawMessage(`{"dsa": {"dsarequired": 2,"pubrender": 0}}`), + }, + }, + }, + giveBid: &entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"dsa":{"adrender": 0}}`), + }, + }, + wantError: ErrNeitherWillRender, + }, + { + name: "required_and_both_will_render", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: json.RawMessage(`{"dsa": {"dsarequired": 2,"pubrender": 2}}`), + }, + }, + }, + giveBid: &entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"dsa":{"adrender": 1}}`), + }, + }, + wantError: ErrBothWillRender, + }, + { + name: "required_and_bid_dsa_is_valid", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: json.RawMessage(`{"dsa": {"dsarequired": 2,"pubrender": 0}}`), + }, + }, + }, + giveBid: &entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"dsa":{"behalf":"` + validBehalf + `","paid":"` + validPaid + `","adrender":1}}`), + }, + }, + wantError: nil, + }, + { + name: "required_and_bid_dsa_is_valid_no_pubrender", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: json.RawMessage(`{"dsa": {"dsarequired": 2}}`), + }, + }, + }, + giveBid: &entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"dsa":{"behalf":"` + validBehalf + `","paid":"` + validPaid + `","adrender":2}}`), + }, + }, + wantError: nil, + }, + { + name: "required_and_bid_dsa_is_valid_no_adrender", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: json.RawMessage(`{"dsa": {"dsarequired": 2, "pubrender": 0}}`), + }, + }, + }, + giveBid: &entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"dsa":{"behalf":"` + validBehalf + `","paid":"` + validPaid + `"}}`), + }, + }, + wantError: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := Validate(tt.giveRequest, tt.giveBid) + if tt.wantError != nil { + assert.Equal(t, err, tt.wantError) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestDSARequired(t *testing.T) { + tests := []struct { + name string + giveReqDSA *openrtb_ext.ExtRegsDSA + wantRequired bool + }{ + { + name: "nil", + giveReqDSA: nil, + wantRequired: false, + }, + { + name: "nil_required", + giveReqDSA: &openrtb_ext.ExtRegsDSA{ + Required: nil, + }, + wantRequired: false, + }, + { + name: "not_required", + giveReqDSA: &openrtb_ext.ExtRegsDSA{ + Required: ptrutil.ToPtr[int8](0), + }, + wantRequired: false, + }, + { + name: "not_required_supported", + giveReqDSA: &openrtb_ext.ExtRegsDSA{ + Required: ptrutil.ToPtr[int8](1), + }, + wantRequired: false, + }, + { + name: "required", + giveReqDSA: &openrtb_ext.ExtRegsDSA{ + Required: ptrutil.ToPtr[int8](2), + }, + wantRequired: true, + }, + { + name: "required_online_platform", + giveReqDSA: &openrtb_ext.ExtRegsDSA{ + Required: ptrutil.ToPtr[int8](3), + }, + wantRequired: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + required := dsaRequired(tt.giveReqDSA) + assert.Equal(t, tt.wantRequired, required) + }) + } +} + +func TestGetReqDSA(t *testing.T) { + tests := []struct { + name string + giveRequest *openrtb_ext.RequestWrapper + expectedDSA *openrtb_ext.ExtRegsDSA + }{ + { + name: "req_is_nil", + giveRequest: nil, + expectedDSA: nil, + }, + { + name: "bidrequest_is_nil", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: nil, + }, + expectedDSA: nil, + }, + { + name: "req.regs_is_nil", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: nil, + }, + }, + expectedDSA: nil, + }, + { + name: "req.regs.ext_is_nil", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: nil, + }, + }, + }, + expectedDSA: nil, + }, + { + name: "req.regs.ext_is_empty", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: json.RawMessage(`{}`), + }, + }, + }, + expectedDSA: nil, + }, + { + name: "req.regs.ext_dsa_is_populated", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: json.RawMessage(`{"dsa": {"dsarequired": 2}}`), + }, + }, + }, + expectedDSA: &openrtb_ext.ExtRegsDSA{ + Required: ptrutil.ToPtr[int8](2), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dsa := getReqDSA(tt.giveRequest) + assert.Equal(t, tt.expectedDSA, dsa) + }) + } +} + +func TestGetBidDSA(t *testing.T) { + tests := []struct { + name string + bid *entities.PbsOrtbBid + expectedDSA *openrtb_ext.ExtBidDSA + }{ + { + name: "bid_is_nil", + bid: nil, + expectedDSA: nil, + }, + { + name: "bid.bid_is_nil", + bid: &entities.PbsOrtbBid{ + Bid: nil, + }, + expectedDSA: nil, + }, + { + name: "bid.bid.ext_is_nil", + bid: &entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + Ext: nil, + }, + }, + expectedDSA: nil, + }, + { + name: "bid.bid.ext_is_empty", + bid: &entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{}`), + }, + }, + expectedDSA: nil, + }, + { + name: "bid.bid.ext.dsa_is_populated", + bid: &entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + Ext: json.RawMessage(`{"dsa": {"behalf":"test1","paid":"test2","adrender":1}}`), + }, + }, + expectedDSA: &openrtb_ext.ExtBidDSA{ + Behalf: "test1", + Paid: "test2", + AdRender: ptrutil.ToPtr[int8](1), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dsa := getBidDSA(tt.bid) + assert.Equal(t, tt.expectedDSA, dsa) + }) + } +} diff --git a/dsa/writer.go b/dsa/writer.go new file mode 100644 index 00000000000..33d0bda6d39 --- /dev/null +++ b/dsa/writer.go @@ -0,0 +1,33 @@ +package dsa + +import ( + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +// Writer is used to write the default DSA to the request (req.regs.ext.dsa) +type Writer struct { + Config *config.AccountDSA + GDPRInScope bool +} + +// Write sets the default DSA object on the request at regs.ext.dsa if it is +// defined in the account config and it is not already present on the request +func (dw Writer) Write(req *openrtb_ext.RequestWrapper) error { + if req == nil || getReqDSA(req) != nil { + return nil + } + if dw.Config == nil || dw.Config.DefaultUnpacked == nil { + return nil + } + if dw.Config.GDPROnly && !dw.GDPRInScope { + return nil + } + regExt, err := req.GetRegExt() + if err != nil { + return err + } + clonedDefaultUnpacked := dw.Config.DefaultUnpacked.Clone() + regExt.SetDSA(clonedDefaultUnpacked) + return nil +} diff --git a/dsa/writer_test.go b/dsa/writer_test.go new file mode 100644 index 00000000000..bbe82e0b693 --- /dev/null +++ b/dsa/writer_test.go @@ -0,0 +1,189 @@ +package dsa + +import ( + "encoding/json" + "testing" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +func TestWrite(t *testing.T) { + requestDSAJSON := json.RawMessage(`{"dsa":{"dsarequired":2,"pubrender":1,"datatopub":1,"transparency":[{"domain":"example1.com","dsaparams":[1,2,3]}]}}`) + defaultDSAJSON := json.RawMessage(`{"dsa":{"dsarequired":3,"pubrender":2,"datatopub":2,"transparency":[{"domain":"example2.com","dsaparams":[4,5,6]}]}}`) + defaultDSA := &openrtb_ext.ExtRegsDSA{ + DataToPub: ptrutil.ToPtr[int8](2), + Required: ptrutil.ToPtr[int8](3), + PubRender: ptrutil.ToPtr[int8](2), + Transparency: []openrtb_ext.ExtBidDSATransparency{ + { + Domain: "example2.com", + Params: []int{4, 5, 6}, + }, + }, + } + + tests := []struct { + name string + giveConfig *config.AccountDSA + giveGDPR bool + giveRequest *openrtb_ext.RequestWrapper + expectRequest *openrtb_ext.RequestWrapper + }{ + { + name: "request_nil", + giveConfig: &config.AccountDSA{ + DefaultUnpacked: defaultDSA, + }, + giveRequest: nil, + expectRequest: nil, + }, + { + name: "config_nil", + giveConfig: nil, + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: nil, + }, + }, + }, + expectRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: nil, + }, + }, + }, + }, + { + name: "config_default_nil", + giveConfig: &config.AccountDSA{ + DefaultUnpacked: nil, + }, + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: nil, + }, + }, + }, + expectRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: nil, + }, + }, + }, + }, + { + name: "request_dsa_present", + giveConfig: &config.AccountDSA{ + DefaultUnpacked: defaultDSA, + }, + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: requestDSAJSON, + }, + }, + }, + expectRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: requestDSAJSON, + }, + }, + }, + }, + { + name: "config_default_present_with_gdpr_only_set_and_gdpr_in_scope", + giveConfig: &config.AccountDSA{ + DefaultUnpacked: defaultDSA, + GDPROnly: true, + }, + giveGDPR: true, + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + expectRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: defaultDSAJSON, + }, + }, + }, + }, + { + name: "config_default_present_with_gdpr_only_set_and_gdpr_not_in_scope", + giveConfig: &config.AccountDSA{ + DefaultUnpacked: defaultDSA, + GDPROnly: true, + }, + giveGDPR: false, + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + expectRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + }, + { + name: "config_default_present_with_gdpr_only_not_set_and_gdpr_in_scope", + giveConfig: &config.AccountDSA{ + DefaultUnpacked: defaultDSA, + GDPROnly: false, + }, + giveGDPR: true, + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + expectRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: defaultDSAJSON, + }, + }, + }, + }, + { + name: "config_default_present_with_gdpr_only_not_set_and_gdpr_not_in_scope", + giveConfig: &config.AccountDSA{ + DefaultUnpacked: defaultDSA, + GDPROnly: false, + }, + giveGDPR: false, + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + expectRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: defaultDSAJSON, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + writer := Writer{ + Config: tt.giveConfig, + GDPRInScope: tt.giveGDPR, + } + err := writer.Write(tt.giveRequest) + + if tt.giveRequest != nil { + tt.giveRequest.RebuildRequest() + assert.Equal(t, tt.expectRequest.BidRequest, tt.giveRequest.BidRequest) + } else { + assert.Nil(t, tt.giveRequest) + } + assert.Nil(t, err) + }) + } +} diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index ff801de988f..51fe8d57514 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -6,31 +6,36 @@ import ( "errors" "fmt" "io" + "math" "net/http" "strconv" "strings" + "time" "github.com/golang/glog" "github.com/julienschmidt/httprouter" gpplib "github.com/prebid/go-gpp" gppConstants "github.com/prebid/go-gpp/constants" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/ccpa" - gppPrivacy "github.com/prebid/prebid-server/privacy/gpp" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/jsonutil" - stringutil "github.com/prebid/prebid-server/util/stringutil" + accountService "github.com/prebid/prebid-server/v3/account" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/gdpr" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/privacy" + "github.com/prebid/prebid-server/v3/privacy/ccpa" + gppPrivacy "github.com/prebid/prebid-server/v3/privacy/gpp" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/usersync" + "github.com/prebid/prebid-server/v3/util/jsonutil" + stringutil "github.com/prebid/prebid-server/v3/util/stringutil" + "github.com/prebid/prebid-server/v3/util/timeutil" ) +const receiveCookieDeprecation = "receive-cookie-deprecation" + var ( errCookieSyncOptOut = errors.New("User has opted out") errCookieSyncBody = errors.New("Failed to read request body") @@ -61,7 +66,7 @@ func NewCookieSyncEndpoint( } return &cookieSyncEndpoint{ - chooser: usersync.NewChooser(syncersByBidder), + chooser: usersync.NewChooser(syncersByBidder, bidderHashSet, config.BidderInfos), config: config, privacyConfig: usersyncPrivacyConfig{ gdprConfig: config.GDPR, @@ -73,6 +78,7 @@ func NewCookieSyncEndpoint( metrics: metrics, pbsAnalytics: analyticsRunner, accountsFetcher: accountsFetcher, + time: &timeutil.RealTime{}, } } @@ -83,10 +89,12 @@ type cookieSyncEndpoint struct { metrics metrics.MetricsEngine pbsAnalytics analytics.Runner accountsFetcher stored_requests.AccountFetcher + time timeutil.Time } func (c *cookieSyncEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - request, privacyMacros, err := c.parseRequest(r) + request, privacyMacros, account, err := c.parseRequest(r) + c.setCookieDeprecationHeader(w, r, account) if err != nil { c.writeParseRequestErrorMetrics(err) c.handleError(w, err, http.StatusBadRequest) @@ -98,30 +106,31 @@ func (c *cookieSyncEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ ht usersync.SyncHostCookie(r, cookie, &c.config.HostCookie) result := c.chooser.Choose(request, cookie) + switch result.Status { case usersync.StatusBlockedByUserOptOut: c.metrics.RecordCookieSync(metrics.CookieSyncOptOut) c.handleError(w, errCookieSyncOptOut, http.StatusUnauthorized) - case usersync.StatusBlockedByGDPR: + case usersync.StatusBlockedByPrivacy: c.metrics.RecordCookieSync(metrics.CookieSyncGDPRHostCookieBlocked) - c.handleResponse(w, request.SyncTypeFilter, cookie, privacyMacros, nil) + c.handleResponse(w, request.SyncTypeFilter, cookie, privacyMacros, nil, result.BiddersEvaluated, request.Debug) case usersync.StatusOK: c.metrics.RecordCookieSync(metrics.CookieSyncOK) c.writeSyncerMetrics(result.BiddersEvaluated) - c.handleResponse(w, request.SyncTypeFilter, cookie, privacyMacros, result.SyncersChosen) + c.handleResponse(w, request.SyncTypeFilter, cookie, privacyMacros, result.SyncersChosen, result.BiddersEvaluated, request.Debug) } } -func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, macros.UserSyncPrivacy, error) { +func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, macros.UserSyncPrivacy, *config.Account, error) { defer r.Body.Close() body, err := io.ReadAll(r.Body) if err != nil { - return usersync.Request{}, macros.UserSyncPrivacy{}, errCookieSyncBody + return usersync.Request{}, macros.UserSyncPrivacy{}, nil, errCookieSyncBody } request := cookieSyncRequest{} if err := jsonutil.UnmarshalValid(body, &request); err != nil { - return usersync.Request{}, macros.UserSyncPrivacy{}, fmt.Errorf("JSON parsing failed: %s", err.Error()) + return usersync.Request{}, macros.UserSyncPrivacy{}, nil, fmt.Errorf("JSON parsing failed: %s", err.Error()) } if request.Account == "" { @@ -129,7 +138,7 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, ma } account, fetchErrs := accountService.GetAccount(context.Background(), c.config, c.accountsFetcher, request.Account, c.metrics) if len(fetchErrs) > 0 { - return usersync.Request{}, macros.UserSyncPrivacy{}, combineErrors(fetchErrs) + return usersync.Request{}, macros.UserSyncPrivacy{}, nil, combineErrors(fetchErrs) } request = c.setLimit(request, account.CookieSync) @@ -137,7 +146,7 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, ma privacyMacros, gdprSignal, privacyPolicies, err := extractPrivacyPolicies(request, c.privacyConfig.gdprConfig.DefaultValue) if err != nil { - return usersync.Request{}, macros.UserSyncPrivacy{}, err + return usersync.Request{}, macros.UserSyncPrivacy{}, account, err } ccpaParsedPolicy := ccpa.ParsedPolicy{} @@ -155,7 +164,7 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, ma syncTypeFilter, err := parseTypeFilter(request.FilterSettings) if err != nil { - return usersync.Request{}, macros.UserSyncPrivacy{}, err + return usersync.Request{}, macros.UserSyncPrivacy{}, account, err } gdprRequestInfo := gdpr.RequestInfo{ @@ -166,22 +175,30 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, ma tcf2Cfg := c.privacyConfig.tcf2ConfigBuilder(c.privacyConfig.gdprConfig.TCF2, account.GDPR) gdprPerms := c.privacyConfig.gdprPermissionsBuilder(tcf2Cfg, gdprRequestInfo) + limit := math.MaxInt + if request.Limit != nil { + limit = *request.Limit + } + rx := usersync.Request{ Bidders: request.Bidders, Cooperative: usersync.Cooperative{ Enabled: (request.CooperativeSync != nil && *request.CooperativeSync) || (request.CooperativeSync == nil && c.config.UserSync.Cooperative.EnabledByDefault), PriorityGroups: c.config.UserSync.PriorityGroups, }, - Limit: request.Limit, + Debug: request.Debug, + Limit: limit, Privacy: usersyncPrivacy{ gdprPermissions: gdprPerms, ccpaParsedPolicy: ccpaParsedPolicy, activityControl: activityControl, activityRequest: privacy.NewRequestFromPolicies(privacyPolicies), + gdprSignal: gdprSignal, }, SyncTypeFilter: syncTypeFilter, + GPPSID: request.GPPSID, } - return rx, privacyMacros, nil + return rx, privacyMacros, account, nil } func extractPrivacyPolicies(request cookieSyncRequest, usersyncDefaultGDPRValue string) (macros.UserSyncPrivacy, gdpr.Signal, privacy.Policies, error) { @@ -198,10 +215,10 @@ func extractPrivacyPolicies(request cookieSyncRequest, usersyncDefaultGDPRValue var gpp gpplib.GppContainer if len(request.GPP) > 0 { - var err error - gpp, err = gpplib.Parse(request.GPP) - if err != nil { - return macros.UserSyncPrivacy{}, gdpr.SignalNo, privacy.Policies{}, err + var errs []error + gpp, errs = gpplib.Parse(request.GPP) + if len(errs) > 0 { + return macros.UserSyncPrivacy{}, gdpr.SignalNo, privacy.Policies{}, errs[0] } } @@ -274,17 +291,38 @@ func (c *cookieSyncEndpoint) writeParseRequestErrorMetrics(err error) { } func (c *cookieSyncEndpoint) setLimit(request cookieSyncRequest, cookieSyncConfig config.CookieSync) cookieSyncRequest { - if request.Limit <= 0 && cookieSyncConfig.DefaultLimit != nil { - request.Limit = *cookieSyncConfig.DefaultLimit + limit := getEffectiveLimit(request.Limit, cookieSyncConfig.DefaultLimit) + maxLimit := getEffectiveMaxLimit(cookieSyncConfig.MaxLimit) + if maxLimit < limit { + request.Limit = &maxLimit + } else { + request.Limit = &limit + } + return request +} + +func getEffectiveLimit(reqLimit *int, defaultLimit *int) int { + limit := reqLimit + + if limit == nil { + limit = defaultLimit } - if cookieSyncConfig.MaxLimit != nil && (request.Limit <= 0 || request.Limit > *cookieSyncConfig.MaxLimit) { - request.Limit = *cookieSyncConfig.MaxLimit + + if limit != nil && *limit > 0 { + return *limit } - if request.Limit < 0 { - request.Limit = 0 + + return math.MaxInt +} + +func getEffectiveMaxLimit(maxLimit *int) int { + limit := maxLimit + + if limit != nil && *limit > 0 { + return *limit } - return request + return math.MaxInt } func (c *cookieSyncEndpoint) setCooperativeSync(request cookieSyncRequest, cookieSyncConfig config.CookieSync) cookieSyncRequest { @@ -387,19 +425,17 @@ func (c *cookieSyncEndpoint) writeSyncerMetrics(biddersEvaluated []usersync.Bidd switch bidder.Status { case usersync.StatusOK: c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncOK) - case usersync.StatusBlockedByGDPR: - c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncPrivacyBlocked) - case usersync.StatusBlockedByCCPA: + case usersync.StatusBlockedByPrivacy: c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncPrivacyBlocked) case usersync.StatusAlreadySynced: c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncAlreadySynced) - case usersync.StatusTypeNotSupported: - c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncTypeNotSupported) + case usersync.StatusRejectedByFilter: + c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncRejectedByFilter) } } } -func (c *cookieSyncEndpoint) handleResponse(w http.ResponseWriter, tf usersync.SyncTypeFilter, co *usersync.Cookie, m macros.UserSyncPrivacy, s []usersync.SyncerChoice) { +func (c *cookieSyncEndpoint) handleResponse(w http.ResponseWriter, tf usersync.SyncTypeFilter, co *usersync.Cookie, m macros.UserSyncPrivacy, s []usersync.SyncerChoice, biddersEvaluated []usersync.BidderEvaluation, debug bool) { status := "no_cookie" if co.HasAnyLiveSyncs() { status = "ok" @@ -429,17 +465,62 @@ func (c *cookieSyncEndpoint) handleResponse(w http.ResponseWriter, tf usersync.S }) } + if debug { + biddersSeen := make(map[string]struct{}) + var debugInfo []cookieSyncResponseDebug + for _, bidderEval := range biddersEvaluated { + var debugResponse cookieSyncResponseDebug + debugResponse.Bidder = bidderEval.Bidder + if bidderEval.Status == usersync.StatusDuplicate && biddersSeen[bidderEval.Bidder] == struct{}{} { + debugResponse.Error = getDebugMessage(bidderEval.Status) + " synced as " + bidderEval.SyncerKey + debugInfo = append(debugInfo, debugResponse) + } else if bidderEval.Status != usersync.StatusOK { + debugResponse.Error = getDebugMessage(bidderEval.Status) + debugInfo = append(debugInfo, debugResponse) + } + biddersSeen[bidderEval.Bidder] = struct{}{} + } + response.Debug = debugInfo + } + c.pbsAnalytics.LogCookieSyncObject(&analytics.CookieSyncObject{ Status: http.StatusOK, BidderStatus: mapBidderStatusToAnalytics(response.BidderStatus), }) w.Header().Set("Content-Type", "application/json; charset=utf-8") + enc := json.NewEncoder(w) enc.SetEscapeHTML(false) enc.Encode(response) } +func (c *cookieSyncEndpoint) setCookieDeprecationHeader(w http.ResponseWriter, r *http.Request, account *config.Account) { + if rcd, err := r.Cookie(receiveCookieDeprecation); err == nil && rcd != nil { + return + } + if account == nil || !account.Privacy.PrivacySandbox.CookieDeprecation.Enabled { + return + } + cookie := &http.Cookie{ + Name: receiveCookieDeprecation, + Value: "1", + Secure: true, + HttpOnly: true, + Path: "/", + SameSite: http.SameSiteNoneMode, + Expires: c.time.Now().Add(time.Second * time.Duration(account.Privacy.PrivacySandbox.CookieDeprecation.TTLSec)), + } + setCookiePartitioned(w, cookie) +} + +// setCookiePartitioned temporary substitute for http.SetCookie(w, cookie) until it supports Partitioned cookie type. Refer https://github.com/golang/go/issues/62490 +func setCookiePartitioned(w http.ResponseWriter, cookie *http.Cookie) { + if v := cookie.String(); v != "" { + w.Header().Add("Set-Cookie", v+"; Partitioned;") + } +} + func mapBidderStatusToAnalytics(from []cookieSyncResponseBidder) []*analytics.CookieSyncBidder { to := make([]*analytics.CookieSyncBidder, len(from)) for i, b := range from { @@ -456,17 +537,40 @@ func mapBidderStatusToAnalytics(from []cookieSyncResponseBidder) []*analytics.Co return to } +func getDebugMessage(status usersync.Status) string { + switch status { + case usersync.StatusAlreadySynced: + return "Already in sync" + case usersync.StatusBlockedByPrivacy: + return "Rejected by privacy" + case usersync.StatusBlockedByUserOptOut: + return "Status blocked by user opt out" + case usersync.StatusDuplicate: + return "Duplicate bidder" + case usersync.StatusUnknownBidder: + return "Unsupported bidder" + case usersync.StatusUnconfiguredBidder: + return "No sync config" + case usersync.StatusRejectedByFilter: + return "Rejected by request filter" + case usersync.StatusBlockedByDisabledUsersync: + return "Sync disabled by config" + } + return "" +} + type cookieSyncRequest struct { Bidders []string `json:"bidders"` GDPR *int `json:"gdpr"` GDPRConsent string `json:"gdpr_consent"` USPrivacy string `json:"us_privacy"` - Limit int `json:"limit"` + Limit *int `json:"limit"` GPP string `json:"gpp"` GPPSID string `json:"gpp_sid"` CooperativeSync *bool `json:"coopSync"` FilterSettings *cookieSyncRequestFilterSettings `json:"filterSettings"` Account string `json:"account"` + Debug bool `json:"debug"` } type cookieSyncRequestFilterSettings struct { @@ -482,6 +586,7 @@ type cookieSyncRequestFilter struct { type cookieSyncResponse struct { Status string `json:"status"` BidderStatus []cookieSyncResponseBidder `json:"bidder_status"` + Debug []cookieSyncResponseDebug `json:"debug,omitempty"` } type cookieSyncResponseBidder struct { @@ -496,6 +601,11 @@ type cookieSyncResponseSync struct { SupportCORS bool `json:"supportCORS,omitempty"` } +type cookieSyncResponseDebug struct { + Bidder string `json:"bidder"` + Error string `json:"error,omitempty"` +} + type usersyncPrivacyConfig struct { gdprConfig config.GDPR gdprPermissionsBuilder gdpr.PermissionsBuilder @@ -509,6 +619,7 @@ type usersyncPrivacy struct { ccpaParsedPolicy ccpa.ParsedPolicy activityControl privacy.ActivityControl activityRequest privacy.ActivityRequest + gdprSignal gdpr.Signal } func (p usersyncPrivacy) GDPRAllowsHostCookie() bool { @@ -532,3 +643,7 @@ func (p usersyncPrivacy) ActivityAllowsUserSync(bidder string) bool { privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidder}, p.activityRequest) } + +func (p usersyncPrivacy) GDPRInScope() bool { + return p.gdprSignal == gdpr.SignalYes +} diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index 1b02357dc5d..e1a7a1d8e14 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -4,30 +4,41 @@ import ( "context" "encoding/json" "errors" + "fmt" "io" + "math" "net/http" "net/http/httptest" "strconv" "strings" "testing" "testing/iotest" - - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/ptrutil" - + "time" + + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/gdpr" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/privacy" + "github.com/prebid/prebid-server/v3/privacy/ccpa" + "github.com/prebid/prebid-server/v3/usersync" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) +// fakeTime implements the Time interface +type fakeTime struct { + time time.Time +} + +func (ft *fakeTime) Now() time.Time { + return ft.time +} + func TestNewCookieSyncEndpoint(t *testing.T) { var ( syncersByBidder = map[string]usersync.Syncer{"a": &MockSyncer{}} @@ -45,15 +56,18 @@ func TestNewCookieSyncEndpoint(t *testing.T) { analytics = MockAnalyticsRunner{} fetcher = FakeAccountsFetcher{} bidders = map[string]openrtb_ext.BidderName{"bidderA": openrtb_ext.BidderName("bidderA"), "bidderB": openrtb_ext.BidderName("bidderB")} + bidderInfo = map[string]config.BidderInfo{"bidderA": {}, "bidderB": {}} + biddersKnown = map[string]struct{}{"bidderA": {}, "bidderB": {}} ) endpoint := NewCookieSyncEndpoint( syncersByBidder, &config.Configuration{ - UserSync: configUserSync, - HostCookie: configHostCookie, - GDPR: configGDPR, - CCPA: config.CCPA{Enforce: configCCPAEnforce}, + UserSync: configUserSync, + HostCookie: configHostCookie, + GDPR: configGDPR, + CCPA: config.CCPA{Enforce: configCCPAEnforce}, + BidderInfos: bidderInfo, }, gdprPermsBuilder, tcf2ConfigBuilder, @@ -65,12 +79,13 @@ func TestNewCookieSyncEndpoint(t *testing.T) { result := endpoint.(*cookieSyncEndpoint) expected := &cookieSyncEndpoint{ - chooser: usersync.NewChooser(syncersByBidder), + chooser: usersync.NewChooser(syncersByBidder, biddersKnown, bidderInfo), config: &config.Configuration{ - UserSync: configUserSync, - HostCookie: configHostCookie, - GDPR: configGDPR, - CCPA: config.CCPA{Enforce: configCCPAEnforce}, + UserSync: configUserSync, + HostCookie: configHostCookie, + GDPR: configGDPR, + CCPA: config.CCPA{Enforce: configCCPAEnforce}, + BidderInfos: bidderInfo, }, privacyConfig: usersyncPrivacyConfig{ gdprConfig: configGDPR, @@ -107,14 +122,16 @@ func TestCookieSyncHandle(t *testing.T) { cookieWithSyncs.Sync("foo", "anyID") testCases := []struct { - description string - givenCookie *usersync.Cookie - givenBody io.Reader - givenChooserResult usersync.Result - expectedStatusCode int - expectedBody string - setMetricsExpectations func(*metrics.MetricsEngineMock) - setAnalyticsExpectations func(*MockAnalyticsRunner) + description string + givenCookie *usersync.Cookie + givenBody io.Reader + givenChooserResult usersync.Result + givenAccountData map[string]json.RawMessage + expectedStatusCode int + expectedBody string + setMetricsExpectations func(*metrics.MetricsEngineMock) + setAnalyticsExpectations func(*MockAnalyticsRunner) + expectedCookieDeprecationHeader bool }{ { description: "Request With Cookie", @@ -231,7 +248,7 @@ func TestCookieSyncHandle(t *testing.T) { givenCookie: cookieWithSyncs, givenBody: strings.NewReader(`{}`), givenChooserResult: usersync.Result{ - Status: usersync.StatusBlockedByGDPR, + Status: usersync.StatusBlockedByPrivacy, BiddersEvaluated: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusOK}}, SyncersChosen: []usersync.SyncerChoice{{Bidder: "a", Syncer: &syncer}}, }, @@ -249,6 +266,74 @@ func TestCookieSyncHandle(t *testing.T) { a.On("LogCookieSyncObject", &expected).Once() }, }, + { + description: "Debug Check", + givenCookie: cookieWithSyncs, + givenBody: strings.NewReader(`{"debug": true}`), + givenChooserResult: usersync.Result{ + Status: usersync.StatusOK, + BiddersEvaluated: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusAlreadySynced}}, + SyncersChosen: []usersync.SyncerChoice{{Bidder: "a", Syncer: &syncer}}, + }, + expectedStatusCode: 200, + expectedBody: `{"status":"ok","bidder_status":[` + + `{"bidder":"a","no_cookie":true,"usersync":{"url":"aURL","type":"redirect","supportCORS":true}}` + + `],"debug":[{"bidder":"a","error":"Already in sync"}]}` + "\n", + setMetricsExpectations: func(m *metrics.MetricsEngineMock) { + m.On("RecordCookieSync", metrics.CookieSyncOK).Once() + m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncAlreadySynced).Once() + }, + setAnalyticsExpectations: func(a *MockAnalyticsRunner) { + expected := analytics.CookieSyncObject{ + Status: 200, + Errors: nil, + BidderStatus: []*analytics.CookieSyncBidder{ + { + BidderCode: "a", + NoCookie: true, + UsersyncInfo: &analytics.UsersyncInfo{URL: "aURL", Type: "redirect", SupportCORS: true}, + }, + }, + } + a.On("LogCookieSyncObject", &expected).Once() + }, + }, + { + description: "CookieDeprecation-Set", + givenCookie: cookieWithSyncs, + givenBody: strings.NewReader(`{"account": "testAccount"}`), + givenChooserResult: usersync.Result{ + Status: usersync.StatusOK, + BiddersEvaluated: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusAlreadySynced}}, + SyncersChosen: []usersync.SyncerChoice{{Bidder: "a", Syncer: &syncer}}, + }, + givenAccountData: map[string]json.RawMessage{ + "testAccount": json.RawMessage(`{"id":"1","privacy":{"privacysandbox":{"cookiedeprecation":{"enabled":true,"ttlsec":86400}}}}`), + }, + expectedStatusCode: 200, + expectedCookieDeprecationHeader: true, + expectedBody: `{"status":"ok","bidder_status":[` + + `{"bidder":"a","no_cookie":true,"usersync":{"url":"aURL","type":"redirect","supportCORS":true}}` + + `]}` + "\n", + setMetricsExpectations: func(m *metrics.MetricsEngineMock) { + m.On("RecordCookieSync", metrics.CookieSyncOK).Once() + m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncAlreadySynced).Once() + }, + setAnalyticsExpectations: func(a *MockAnalyticsRunner) { + expected := analytics.CookieSyncObject{ + Status: 200, + Errors: nil, + BidderStatus: []*analytics.CookieSyncBidder{ + { + BidderCode: "a", + NoCookie: true, + UsersyncInfo: &analytics.UsersyncInfo{URL: "aURL", Type: "redirect", SupportCORS: true}, + }, + }, + } + a.On("LogCookieSyncObject", &expected).Once() + }, + }, } for _, test := range testCases { @@ -258,7 +343,9 @@ func TestCookieSyncHandle(t *testing.T) { mockAnalytics := MockAnalyticsRunner{} test.setAnalyticsExpectations(&mockAnalytics) - fakeAccountFetcher := FakeAccountsFetcher{} + fakeAccountFetcher := FakeAccountsFetcher{ + AccountData: test.givenAccountData, + } gdprPermsBuilder := fakePermissionsBuilder{ permissions: &fakePermissions{}, @@ -293,6 +380,7 @@ func TestCookieSyncHandle(t *testing.T) { metrics: &mockMetrics, pbsAnalytics: &mockAnalytics, accountsFetcher: &fakeAccountFetcher, + time: &fakeTime{time: time.Date(2024, 2, 22, 9, 42, 4, 13, time.UTC)}, } assert.NoError(t, endpoint.config.MarshalAccountDefaults()) @@ -300,6 +388,16 @@ func TestCookieSyncHandle(t *testing.T) { assert.Equal(t, test.expectedStatusCode, writer.Code, test.description+":status_code") assert.Equal(t, test.expectedBody, writer.Body.String(), test.description+":body") + + gotCookie := writer.Header().Get("Set-Cookie") + if test.expectedCookieDeprecationHeader { + wantCookieTTL := endpoint.time.Now().Add(time.Second * time.Duration(86400)).UTC().Format(http.TimeFormat) + wantCookie := fmt.Sprintf("receive-cookie-deprecation=1; Path=/; Expires=%v; HttpOnly; Secure; SameSite=None; Partitioned;", wantCookieTTL) + assert.Equal(t, wantCookie, gotCookie, test.description) + } else { + assert.Empty(t, gotCookie, test.description) + } + mockMetrics.AssertExpectations(t) mockAnalytics.AssertExpectations(t) } @@ -538,11 +636,13 @@ func TestCookieSyncParseRequest(t *testing.T) { gdprPermissions: &fakePermissions{}, ccpaParsedPolicy: expectedCCPAParsedPolicy, activityRequest: privacy.NewRequestFromPolicies(privacy.Policies{GPPSID: []int8{2}}), + gdprSignal: 1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), Redirect: usersync.NewSpecificBidderFilter([]string{"b"}, usersync.BidderFilterModeExclude), }, + GPPSID: "2", }, }, { @@ -578,6 +678,7 @@ func TestCookieSyncParseRequest(t *testing.T) { gdprPermissions: &fakePermissions{}, ccpaParsedPolicy: expectedCCPAParsedPolicy, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: 1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -592,9 +693,11 @@ func TestCookieSyncParseRequest(t *testing.T) { givenCCPAEnabled: true, expectedPrivacy: macros.UserSyncPrivacy{}, expectedRequest: usersync.Request{ + Limit: math.MaxInt, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -619,9 +722,11 @@ func TestCookieSyncParseRequest(t *testing.T) { Enabled: true, PriorityGroups: [][]string{{"a", "b", "c"}}, }, + Limit: math.MaxInt, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -646,9 +751,11 @@ func TestCookieSyncParseRequest(t *testing.T) { Enabled: false, PriorityGroups: [][]string{{"a", "b", "c"}}, }, + Limit: math.MaxInt, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -673,9 +780,11 @@ func TestCookieSyncParseRequest(t *testing.T) { Enabled: false, PriorityGroups: [][]string{{"a", "b", "c"}}, }, + Limit: math.MaxInt, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -700,9 +809,11 @@ func TestCookieSyncParseRequest(t *testing.T) { Enabled: false, PriorityGroups: [][]string{{"a", "b", "c"}}, }, + Limit: math.MaxInt, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -727,9 +838,11 @@ func TestCookieSyncParseRequest(t *testing.T) { Enabled: true, PriorityGroups: [][]string{{"a", "b", "c"}}, }, + Limit: math.MaxInt, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -754,9 +867,11 @@ func TestCookieSyncParseRequest(t *testing.T) { Enabled: true, PriorityGroups: [][]string{{"a", "b", "c"}}, }, + Limit: math.MaxInt, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -771,9 +886,11 @@ func TestCookieSyncParseRequest(t *testing.T) { givenCCPAEnabled: true, expectedPrivacy: macros.UserSyncPrivacy{}, expectedRequest: usersync.Request{ + Limit: math.MaxInt, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -790,9 +907,11 @@ func TestCookieSyncParseRequest(t *testing.T) { USPrivacy: "1NYN", }, expectedRequest: usersync.Request{ + Limit: math.MaxInt, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -830,9 +949,11 @@ func TestCookieSyncParseRequest(t *testing.T) { GDPR: "0", }, expectedRequest: usersync.Request{ + Limit: math.MaxInt, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: 0, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -856,9 +977,11 @@ func TestCookieSyncParseRequest(t *testing.T) { GDPR: "", }, expectedRequest: usersync.Request{ + Limit: math.MaxInt, Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -904,6 +1027,7 @@ func TestCookieSyncParseRequest(t *testing.T) { Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -936,6 +1060,7 @@ func TestCookieSyncParseRequest(t *testing.T) { Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -968,6 +1093,7 @@ func TestCookieSyncParseRequest(t *testing.T) { Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: 0, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -1007,7 +1133,7 @@ func TestCookieSyncParseRequest(t *testing.T) { }}, } assert.NoError(t, endpoint.config.MarshalAccountDefaults()) - request, privacyPolicies, err := endpoint.parseRequest(httpRequest) + request, privacyPolicies, _, err := endpoint.parseRequest(httpRequest) if test.expectedError == "" { assert.NoError(t, err, test.description+":err") @@ -1021,152 +1147,242 @@ func TestCookieSyncParseRequest(t *testing.T) { } } -func TestSetLimit(t *testing.T) { - intNegative1 := -1 - int20 := 20 - int30 := 30 - int40 := 40 +func TestGetEffectiveLimit(t *testing.T) { + intNegative := ptrutil.ToPtr(-1) + int0 := ptrutil.ToPtr(0) + int30 := ptrutil.ToPtr(30) + int40 := ptrutil.ToPtr(40) + intMax := ptrutil.ToPtr(math.MaxInt) + + tests := []struct { + name string + reqLimit *int + defaultLimit *int + expectedLimit int + }{ + { + name: "nil", + reqLimit: nil, + defaultLimit: nil, + expectedLimit: math.MaxInt, + }, + { + name: "req_limit_negative", + reqLimit: intNegative, + defaultLimit: nil, + expectedLimit: math.MaxInt, + }, + { + name: "req_limit_zero", + reqLimit: int0, + defaultLimit: nil, + expectedLimit: math.MaxInt, + }, + { + name: "req_limit_in_range", + reqLimit: int30, + defaultLimit: nil, + expectedLimit: 30, + }, + { + name: "req_limit_at_max", + reqLimit: intMax, + defaultLimit: nil, + expectedLimit: math.MaxInt, + }, + { + name: "default_limit_negative", + reqLimit: nil, + defaultLimit: intNegative, + expectedLimit: math.MaxInt, + }, + { + name: "default_limit_zero", + reqLimit: nil, + defaultLimit: intNegative, + expectedLimit: math.MaxInt, + }, + { + name: "default_limit_in_range", + reqLimit: nil, + defaultLimit: int30, + expectedLimit: 30, + }, + { + name: "default_limit_at_max", + reqLimit: nil, + defaultLimit: intMax, + expectedLimit: math.MaxInt, + }, + { + name: "both_in_range", + reqLimit: int30, + defaultLimit: int40, + expectedLimit: 30, + }, + } - testCases := []struct { - description string - givenRequest cookieSyncRequest - givenAccount *config.Account - expectedRequest cookieSyncRequest + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := getEffectiveLimit(test.reqLimit, test.defaultLimit) + assert.Equal(t, test.expectedLimit, result) + }) + } +} + +func TestGetEffectiveMaxLimit(t *testing.T) { + intNegative := ptrutil.ToPtr(-1) + int0 := ptrutil.ToPtr(0) + int30 := ptrutil.ToPtr(30) + intMax := ptrutil.ToPtr(math.MaxInt) + + tests := []struct { + name string + maxLimit *int + expectedLimit int }{ { - description: "Default Limit is Applied (request limit = 0)", - givenRequest: cookieSyncRequest{ - Limit: 0, - }, - givenAccount: &config.Account{ - CookieSync: config.CookieSync{ - DefaultLimit: &int20, - }, - }, - expectedRequest: cookieSyncRequest{ - Limit: 20, - }, + name: "nil", + maxLimit: nil, + expectedLimit: math.MaxInt, }, { - description: "Default Limit is Not Applied (default limit not set)", - givenRequest: cookieSyncRequest{ - Limit: 0, - }, - givenAccount: &config.Account{ - CookieSync: config.CookieSync{ - DefaultLimit: nil, - }, - }, - expectedRequest: cookieSyncRequest{ - Limit: 0, - }, + name: "req_limit_negative", + maxLimit: intNegative, + expectedLimit: math.MaxInt, }, { - description: "Default Limit is Not Applied (request limit > 0)", - givenRequest: cookieSyncRequest{ - Limit: 10, - }, - givenAccount: &config.Account{ - CookieSync: config.CookieSync{ - DefaultLimit: &int20, - }, - }, - expectedRequest: cookieSyncRequest{ - Limit: 10, - }, + name: "req_limit_zero", + maxLimit: int0, + expectedLimit: math.MaxInt, }, { - description: "Max Limit is Applied (request limit <= 0)", + name: "req_limit_in_range", + maxLimit: int30, + expectedLimit: 30, + }, + { + name: "req_limit_too_large", + maxLimit: intMax, + expectedLimit: math.MaxInt, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := getEffectiveMaxLimit(test.maxLimit) + assert.Equal(t, test.expectedLimit, result) + }) + } +} + +func TestSetLimit(t *testing.T) { + intNegative := ptrutil.ToPtr(-1) + int0 := ptrutil.ToPtr(0) + int10 := ptrutil.ToPtr(10) + int20 := ptrutil.ToPtr(20) + int30 := ptrutil.ToPtr(30) + intMax := ptrutil.ToPtr(math.MaxInt) + + tests := []struct { + name string + givenRequest cookieSyncRequest + givenAccount *config.Account + expectedRequest cookieSyncRequest + }{ + { + name: "nil_limits", givenRequest: cookieSyncRequest{ - Limit: 0, + Limit: nil, }, givenAccount: &config.Account{ CookieSync: config.CookieSync{ - MaxLimit: &int30, + DefaultLimit: nil, + MaxLimit: nil, }, }, expectedRequest: cookieSyncRequest{ - Limit: 30, + Limit: intMax, }, }, { - description: "Max Limit is Applied (0 < max < limit)", + name: "limit_negative", givenRequest: cookieSyncRequest{ - Limit: 40, + Limit: intNegative, }, givenAccount: &config.Account{ CookieSync: config.CookieSync{ - MaxLimit: &int30, + DefaultLimit: int20, }, }, expectedRequest: cookieSyncRequest{ - Limit: 30, + Limit: intMax, }, }, { - description: "Max Limit is Not Applied (max not set)", + name: "limit_zero", givenRequest: cookieSyncRequest{ - Limit: 10, + Limit: int0, }, givenAccount: &config.Account{ CookieSync: config.CookieSync{ - MaxLimit: nil, + DefaultLimit: int20, }, }, expectedRequest: cookieSyncRequest{ - Limit: 10, + Limit: intMax, }, }, { - description: "Max Limit is Not Applied (0 < limit < max)", + name: "limit_less_than_max", givenRequest: cookieSyncRequest{ - Limit: 10, + Limit: int10, }, givenAccount: &config.Account{ CookieSync: config.CookieSync{ - MaxLimit: &int30, + DefaultLimit: int20, + MaxLimit: int30, }, }, expectedRequest: cookieSyncRequest{ - Limit: 10, + Limit: int10, }, }, { - description: "Max Limit is Applied After applying the default", + name: "limit_greater_than_max", givenRequest: cookieSyncRequest{ - Limit: 0, + Limit: int30, }, givenAccount: &config.Account{ CookieSync: config.CookieSync{ - DefaultLimit: &int40, - MaxLimit: &int30, + DefaultLimit: int20, + MaxLimit: int10, }, }, expectedRequest: cookieSyncRequest{ - Limit: 30, + Limit: int10, }, }, { - description: "Negative Value Check", + name: "limit_at_max", givenRequest: cookieSyncRequest{ - Limit: 0, + Limit: intMax, }, givenAccount: &config.Account{ - CookieSync: config.CookieSync{ - DefaultLimit: &intNegative1, - MaxLimit: &intNegative1, - }, + CookieSync: config.CookieSync{}, }, expectedRequest: cookieSyncRequest{ - Limit: 0, + Limit: intMax, }, }, } - for _, test := range testCases { - endpoint := cookieSyncEndpoint{} - request := endpoint.setLimit(test.givenRequest, test.givenAccount.CookieSync) - assert.Equal(t, test.expectedRequest, request, test.description) + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + endpoint := cookieSyncEndpoint{} + request := endpoint.setLimit(test.givenRequest, test.givenAccount.CookieSync) + assert.Equal(t, test.expectedRequest, request) + }) } } @@ -1508,14 +1724,14 @@ func TestCookieSyncWriteBidderMetrics(t *testing.T) { }, { description: "One - Blocked By GDPR", - given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusBlockedByGDPR}}, + given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusBlockedByPrivacy}}, setExpectations: func(m *metrics.MetricsEngineMock) { m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncPrivacyBlocked).Once() }, }, { description: "One - Blocked By CCPA", - given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusBlockedByCCPA}}, + given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusBlockedByPrivacy}}, setExpectations: func(m *metrics.MetricsEngineMock) { m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncPrivacyBlocked).Once() }, @@ -1528,10 +1744,10 @@ func TestCookieSyncWriteBidderMetrics(t *testing.T) { }, }, { - description: "One - Type Not Supported", - given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusTypeNotSupported}}, + description: "One - Rejected By Filter", + given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusRejectedByFilter}}, setExpectations: func(m *metrics.MetricsEngineMock) { - m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncTypeNotSupported).Once() + m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncRejectedByFilter).Once() }, }, { @@ -1580,10 +1796,22 @@ func TestCookieSyncHandleResponse(t *testing.T) { syncerWithError := MockSyncer{} syncerWithError.On("GetSync", syncTypeExpected, privacyMacros).Return(syncWithError, errors.New("anyError")).Maybe() + bidderEvalForDebug := []usersync.BidderEvaluation{ + {Bidder: "Bidder1", Status: usersync.StatusAlreadySynced}, + {Bidder: "Bidder2", Status: usersync.StatusUnknownBidder}, + {Bidder: "Bidder3", Status: usersync.StatusUnconfiguredBidder}, + {Bidder: "Bidder4", Status: usersync.StatusBlockedByPrivacy}, + {Bidder: "Bidder5", Status: usersync.StatusRejectedByFilter}, + {Bidder: "Bidder6", Status: usersync.StatusBlockedByUserOptOut}, + {Bidder: "Bidder7", Status: usersync.StatusBlockedByDisabledUsersync}, + {Bidder: "BidderA", Status: usersync.StatusDuplicate, SyncerKey: "syncerB"}, + } + testCases := []struct { description string givenCookieHasSyncs bool givenSyncersChosen []usersync.SyncerChoice + givenDebug bool expectedJSON string expectedAnalytics analytics.CookieSyncObject }{ @@ -1661,6 +1889,14 @@ func TestCookieSyncHandleResponse(t *testing.T) { expectedJSON: `{"status":"no_cookie","bidder_status":[]}` + "\n", expectedAnalytics: analytics.CookieSyncObject{Status: 200, BidderStatus: []*analytics.CookieSyncBidder{}}, }, + { + description: "Debug is true, should see all rejected bidder eval statuses in response", + givenCookieHasSyncs: true, + givenDebug: true, + givenSyncersChosen: []usersync.SyncerChoice{}, + expectedJSON: `{"status":"ok","bidder_status":[],"debug":[{"bidder":"Bidder1","error":"Already in sync"},{"bidder":"Bidder2","error":"Unsupported bidder"},{"bidder":"Bidder3","error":"No sync config"},{"bidder":"Bidder4","error":"Rejected by privacy"},{"bidder":"Bidder5","error":"Rejected by request filter"},{"bidder":"Bidder6","error":"Status blocked by user opt out"},{"bidder":"Bidder7","error":"Sync disabled by config"},{"bidder":"BidderA","error":"Duplicate bidder synced as syncerB"}]}` + "\n", + expectedAnalytics: analytics.CookieSyncObject{Status: 200, BidderStatus: []*analytics.CookieSyncBidder{}}, + }, } for _, test := range testCases { @@ -1676,7 +1912,14 @@ func TestCookieSyncHandleResponse(t *testing.T) { writer := httptest.NewRecorder() endpoint := cookieSyncEndpoint{pbsAnalytics: &mockAnalytics} - endpoint.handleResponse(writer, syncTypeFilter, cookie, privacyMacros, test.givenSyncersChosen) + + var bidderEval []usersync.BidderEvaluation + if test.givenDebug { + bidderEval = bidderEvalForDebug + } else { + bidderEval = []usersync.BidderEvaluation{} + } + endpoint.handleResponse(writer, syncTypeFilter, cookie, privacyMacros, test.givenSyncersChosen, bidderEval, test.givenDebug) if assert.Equal(t, writer.Code, http.StatusOK, test.description+":http_status") { assert.Equal(t, writer.Header().Get("Content-Type"), "application/json; charset=utf-8", test.description+":http_header") @@ -1915,6 +2158,39 @@ func TestCookieSyncActivityControlIntegration(t *testing.T) { } } +func TestUsersyncPrivacyGDPRInScope(t *testing.T) { + testCases := []struct { + description string + givenGdprSignal gdpr.Signal + expected bool + }{ + { + description: "GDPR Signal Yes", + givenGdprSignal: gdpr.SignalYes, + expected: true, + }, + { + description: "GDPR Signal No", + givenGdprSignal: gdpr.SignalNo, + expected: false, + }, + { + description: "GDPR Signal Ambigious", + givenGdprSignal: gdpr.SignalAmbiguous, + expected: false, + }, + } + + for _, test := range testCases { + privacy := usersyncPrivacy{ + gdprSignal: test.givenGdprSignal, + } + + result := privacy.GDPRInScope() + assert.Equal(t, test.expected, result, test.description) + } +} + func TestCombineErrors(t *testing.T) { testCases := []struct { description string @@ -1981,7 +2257,7 @@ func (m *MockSyncer) Key() string { return args.String(0) } -func (m *MockSyncer) DefaultSyncType() usersync.SyncType { +func (m *MockSyncer) DefaultResponseFormat() usersync.SyncType { args := m.Called() return args.Get(0).(usersync.SyncType) } @@ -2024,6 +2300,10 @@ func (m *MockAnalyticsRunner) LogNotificationEventObject(obj *analytics.Notifica m.Called(obj, ac) } +func (m *MockAnalyticsRunner) Shutdown() { + m.Called() +} + type MockGDPRPerms struct { mock.Mock } @@ -2038,17 +2318,17 @@ func (m *MockGDPRPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ex return args.Bool(0), args.Error(1) } -func (m *MockGDPRPerms) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) { +func (m *MockGDPRPerms) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) gdpr.AuctionPermissions { args := m.Called(ctx, bidderCoreName, bidder) - return args.Get(0).(gdpr.AuctionPermissions), args.Error(1) + return args.Get(0).(gdpr.AuctionPermissions) } type FakeAccountsFetcher struct { AccountData map[string]json.RawMessage } -func (f FakeAccountsFetcher) FetchAccount(ctx context.Context, defaultAccountJSON json.RawMessage, accountID string) (json.RawMessage, []error) { - defaultAccountJSON = json.RawMessage(`{"disabled":false}`) +func (f FakeAccountsFetcher) FetchAccount(ctx context.Context, _ json.RawMessage, accountID string) (json.RawMessage, []error) { + defaultAccountJSON := json.RawMessage(`{"disabled":false}`) if accountID == metrics.PublisherUnknown { return defaultAccountJSON, nil @@ -2070,10 +2350,10 @@ func (p *fakePermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return true, nil } -func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) { +func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) gdpr.AuctionPermissions { return gdpr.AuctionPermissions{ AllowBidRequest: true, - }, nil + } } func getDefaultActivityConfig(componentName string, allow bool) *config.AccountPrivacy { @@ -2094,3 +2374,131 @@ func getDefaultActivityConfig(componentName string, allow bool) *config.AccountP }, } } + +func TestSetCookieDeprecationHeader(t *testing.T) { + getTestRequest := func(addCookie bool) *http.Request { + r := httptest.NewRequest("POST", "/cookie_sync", nil) + if addCookie { + r.AddCookie(&http.Cookie{Name: receiveCookieDeprecation, Value: "1"}) + } + return r + } + + tests := []struct { + name string + responseWriter http.ResponseWriter + request *http.Request + account *config.Account + expectedCookieDeprecationHeader bool + }{ + { + name: "not-present-account-nil", + request: getTestRequest(false), + responseWriter: httptest.NewRecorder(), + account: nil, + expectedCookieDeprecationHeader: false, + }, + { + name: "not-present-cookiedeprecation-disabled", + request: getTestRequest(false), + responseWriter: httptest.NewRecorder(), + account: &config.Account{ + Privacy: config.AccountPrivacy{ + PrivacySandbox: config.PrivacySandbox{ + CookieDeprecation: config.CookieDeprecation{ + Enabled: false, + }, + }, + }, + }, + expectedCookieDeprecationHeader: false, + }, + { + name: "present-cookiedeprecation-disabled", + request: getTestRequest(true), + responseWriter: httptest.NewRecorder(), + account: &config.Account{ + Privacy: config.AccountPrivacy{ + PrivacySandbox: config.PrivacySandbox{ + CookieDeprecation: config.CookieDeprecation{ + Enabled: false, + }, + }, + }, + }, + expectedCookieDeprecationHeader: false, + }, + { + name: "present-cookiedeprecation-enabled", + request: getTestRequest(true), + responseWriter: httptest.NewRecorder(), + account: &config.Account{ + Privacy: config.AccountPrivacy{ + PrivacySandbox: config.PrivacySandbox{ + CookieDeprecation: config.CookieDeprecation{ + Enabled: true, + TTLSec: 86400, + }, + }, + }, + }, + + expectedCookieDeprecationHeader: false, + }, + { + name: "present-account-nil", + request: getTestRequest(true), + responseWriter: httptest.NewRecorder(), + account: nil, + expectedCookieDeprecationHeader: false, + }, + { + name: "not-present-cookiedeprecation-enabled", + request: getTestRequest(false), + responseWriter: httptest.NewRecorder(), + account: &config.Account{ + Privacy: config.AccountPrivacy{ + PrivacySandbox: config.PrivacySandbox{ + CookieDeprecation: config.CookieDeprecation{ + Enabled: true, + TTLSec: 86400, + }, + }, + }, + }, + expectedCookieDeprecationHeader: true, + }, + { + name: "failed-to-read-cookiedeprecation-enabled", + request: &http.Request{}, // nil cookie. error: http: named cookie not present + responseWriter: httptest.NewRecorder(), + account: &config.Account{ + Privacy: config.AccountPrivacy{ + PrivacySandbox: config.PrivacySandbox{ + CookieDeprecation: config.CookieDeprecation{ + Enabled: true, + TTLSec: 86400, + }, + }, + }, + }, + expectedCookieDeprecationHeader: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &cookieSyncEndpoint{ + time: &fakeTime{time: time.Date(2024, 2, 22, 9, 42, 4, 13, time.UTC)}, + } + c.setCookieDeprecationHeader(tt.responseWriter, tt.request, tt.account) + gotCookie := tt.responseWriter.Header().Get("Set-Cookie") + if tt.expectedCookieDeprecationHeader { + wantCookieTTL := c.time.Now().Add(time.Second * time.Duration(86400)).UTC().Format(http.TimeFormat) + wantCookie := fmt.Sprintf("receive-cookie-deprecation=1; Path=/; Expires=%v; HttpOnly; Secure; SameSite=None; Partitioned;", wantCookieTTL) + assert.Equal(t, wantCookie, gotCookie, ":set_cookie_deprecation_header") + } else { + assert.Empty(t, gotCookie, ":set_cookie_deprecation_header") + } + }) + } +} diff --git a/endpoints/currency_rates.go b/endpoints/currency_rates.go index 49ae9963cd9..9d27fb2497d 100644 --- a/endpoints/currency_rates.go +++ b/endpoints/currency_rates.go @@ -5,8 +5,8 @@ import ( "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) // currencyRatesInfo holds currency rates information. diff --git a/endpoints/currency_rates_test.go b/endpoints/currency_rates_test.go index 7fc513e7dbe..00da093a2d5 100644 --- a/endpoints/currency_rates_test.go +++ b/endpoints/currency_rates_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/v3/currency" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/events/account_test.go b/endpoints/events/account_test.go index 8477b43b49b..b379621b061 100644 --- a/endpoints/events/account_test.go +++ b/endpoints/events/account_test.go @@ -9,10 +9,10 @@ import ( "testing" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/stored_requests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/endpoints/events/event.go b/endpoints/events/event.go index ffcb4e006e1..fa2fca2f96c 100644 --- a/endpoints/events/event.go +++ b/endpoints/events/event.go @@ -4,22 +4,23 @@ import ( "context" "errors" "fmt" - "github.com/prebid/prebid-server/openrtb_ext" "net/http" "net/url" "strconv" "time" "unicode" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/julienschmidt/httprouter" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/httputil" + accountService "github.com/prebid/prebid-server/v3/account" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/privacy" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/util/httputil" ) const ( @@ -69,7 +70,7 @@ func (e *eventEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httprou w.WriteHeader(http.StatusBadRequest) for _, err := range errs { - w.Write([]byte(fmt.Sprintf("invalid request: %s\n", err.Error()))) + fmt.Fprintf(w, "invalid request: %s\n", err.Error()) } return @@ -80,7 +81,7 @@ func (e *eventEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httprou if err != nil { w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte(fmt.Sprintf("Account '%s' is required query parameter and can't be empty", AccountIdParameter))) + fmt.Fprintf(w, "Account '%s' is required query parameter and can't be empty", AccountIdParameter) return } eventRequest.AccountID = accountId @@ -104,7 +105,7 @@ func (e *eventEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httprou w.WriteHeader(status) for _, message := range messages { - w.Write([]byte(fmt.Sprintf("Invalid request: %s\n", message))) + fmt.Fprintf(w, "Invalid request: %s\n", message) } return } @@ -112,7 +113,7 @@ func (e *eventEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httprou // Check if events are enabled for the account if !account.Events.Enabled { w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte(fmt.Sprintf("Account '%s' doesn't support events", eventRequest.AccountID))) + fmt.Fprintf(w, "Account '%s' doesn't support events", eventRequest.AccountID) return } @@ -215,7 +216,7 @@ func HandleAccountServiceErrors(errs []error) (status int, messages []string) { errCode := errortypes.ReadCode(er) - if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.AccountDisabledErrorCode { + if errCode == errortypes.BlockedAppErrorCode || errCode == errortypes.AccountDisabledErrorCode { status = http.StatusServiceUnavailable } if errCode == errortypes.MalformedAcctErrorCode { diff --git a/endpoints/events/event_test.go b/endpoints/events/event_test.go index 5fc115786fe..eb52d8c1287 100644 --- a/endpoints/events/event_test.go +++ b/endpoints/events/event_test.go @@ -12,12 +12,12 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/privacy" + "github.com/prebid/prebid-server/v3/stored_requests" "github.com/stretchr/testify/assert" ) @@ -64,6 +64,8 @@ func (e *eventsMockAnalyticsModule) LogNotificationEventObject(ne *analytics.Not e.Invoked = true } +func (e *eventsMockAnalyticsModule) Shutdown() {} + var mockAccountData = map[string]json.RawMessage{ "events_enabled": json.RawMessage(`{"events": {"enabled":true}}`), "events_disabled": json.RawMessage(`{"events": {"enabled":false}}`), diff --git a/endpoints/events/vtrack.go b/endpoints/events/vtrack.go index eeb409e24ae..590243bb5c2 100644 --- a/endpoints/events/vtrack.go +++ b/endpoints/events/vtrack.go @@ -11,15 +11,15 @@ import ( "github.com/golang/glog" "github.com/julienschmidt/httprouter" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/jsonutil" + accountService "github.com/prebid/prebid-server/v3/account" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/prebid_cache_client" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) const ( @@ -29,15 +29,13 @@ const ( ImpressionOpenTag = "" ) -type normalizeBidderName func(name string) (openrtb_ext.BidderName, bool) - type vtrackEndpoint struct { Cfg *config.Configuration Accounts stored_requests.AccountFetcher BidderInfos config.BidderInfos Cache prebid_cache_client.Client MetricsEngine metrics.MetricsEngine - normalizeBidderName normalizeBidderName + normalizeBidderName openrtb_ext.BidderNameNormalizer } type BidCacheRequest struct { @@ -74,7 +72,7 @@ func (v *vtrackEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httpro // account id is required if accountId == "" { w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(fmt.Sprintf("Account '%s' is required query parameter and can't be empty", AccountParameter))) + fmt.Fprintf(w, "Account '%s' is required query parameter and can't be empty", AccountParameter) return } @@ -82,7 +80,7 @@ func (v *vtrackEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httpro integrationType, err := getIntegrationType(r) if err != nil { w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(fmt.Sprintf("Invalid integration type: %s\n", err.Error()))) + fmt.Fprintf(w, "Invalid integration type: %s\n", err.Error()) return } @@ -92,7 +90,7 @@ func (v *vtrackEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httpro // check if there was any error while parsing puts request if err != nil { w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(fmt.Sprintf("Invalid request: %s\n", err.Error()))) + fmt.Fprintf(w, "Invalid request: %s\n", err.Error()) return } @@ -106,7 +104,7 @@ func (v *vtrackEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httpro w.WriteHeader(status) for _, message := range messages { - w.Write([]byte(fmt.Sprintf("Invalid request: %s\n", message))) + fmt.Fprintf(w, "Invalid request: %s\n", message) } return } @@ -118,7 +116,7 @@ func (v *vtrackEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httpro if len(errs) > 0 { w.WriteHeader(http.StatusInternalServerError) for _, err := range errs { - w.Write([]byte(fmt.Sprintf("Error(s) updating vast: %s\n", err.Error()))) + fmt.Fprintf(w, "Error(s) updating vast: %s\n", err.Error()) return } @@ -128,7 +126,7 @@ func (v *vtrackEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httpro if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("Error serializing pbs cache response: %s\n", err.Error()))) + fmt.Fprintf(w, "Error serializing pbs cache response: %s\n", err.Error()) return } @@ -194,12 +192,12 @@ func ParseVTrackRequest(httpRequest *http.Request, maxRequestSize int64) (req *B for _, bcr := range req.Puts { if bcr.BidID == "" { - err = error(&errortypes.BadInput{Message: fmt.Sprint("'bidid' is required field and can't be empty")}) + err = error(&errortypes.BadInput{Message: "'bidid' is required field and can't be empty"}) return req, err } if bcr.Bidder == "" { - err = error(&errortypes.BadInput{Message: fmt.Sprint("'bidder' is required field and can't be empty")}) + err = error(&errortypes.BadInput{Message: "'bidder' is required field and can't be empty"}) return req, err } } @@ -257,7 +255,7 @@ func (v *vtrackEndpoint) cachePutObjects(ctx context.Context, req *BidCacheReque } // getBiddersAllowingVastUpdate returns a list of bidders that allow VAST XML modification -func getBiddersAllowingVastUpdate(req *BidCacheRequest, bidderInfos *config.BidderInfos, allowUnknownBidder bool, normalizeBidderName normalizeBidderName) map[string]struct{} { +func getBiddersAllowingVastUpdate(req *BidCacheRequest, bidderInfos *config.BidderInfos, allowUnknownBidder bool, normalizeBidderName openrtb_ext.BidderNameNormalizer) map[string]struct{} { bl := map[string]struct{}{} for _, bcr := range req.Puts { @@ -270,13 +268,14 @@ func getBiddersAllowingVastUpdate(req *BidCacheRequest, bidderInfos *config.Bidd } // isAllowVastForBidder checks if a bidder is active and allowed to modify vast xml data -func isAllowVastForBidder(bidder string, bidderInfos *config.BidderInfos, allowUnknownBidder bool, normalizeBidderName normalizeBidderName) bool { - //if bidder is active and isModifyingVastXmlAllowed is true +func isAllowVastForBidder(bidder string, bidderInfos *config.BidderInfos, allowUnknownBidder bool, normalizeBidderName openrtb_ext.BidderNameNormalizer) bool { + // if bidder is active and isModifyingVastXmlAllowed is true // check if bidder is configured if normalizedBidder, ok := normalizeBidderName(bidder); ok { - if b, ok := (*bidderInfos)[normalizedBidder.String()]; bidderInfos != nil && ok { - // check if bidder is enabled - return b.IsEnabled() && b.ModifyingVastXmlAllowed + if bidderInfos != nil { + if b, ok := (*bidderInfos)[normalizedBidder.String()]; ok { + return b.IsEnabled() && b.ModifyingVastXmlAllowed + } } } diff --git a/endpoints/events/vtrack_test.go b/endpoints/events/vtrack_test.go index d950a443afe..1345d563db5 100644 --- a/endpoints/events/vtrack_test.go +++ b/endpoints/events/vtrack_test.go @@ -12,11 +12,11 @@ import ( "strings" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/prebid_cache_client" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -471,7 +471,7 @@ func TestShouldSendToCacheExpectedPutsAndUpdatableBiddersWhenBidderVastAllowed(t recorder := httptest.NewRecorder() - var mockNormalizeBidderName normalizeBidderName = func(name string) (openrtb_ext.BidderName, bool) { + var mockNormalizeBidderName openrtb_ext.BidderNameNormalizer = func(name string) (openrtb_ext.BidderName, bool) { return openrtb_ext.BidderName(name), true } e := vtrackEndpoint{ diff --git a/endpoints/getuids.go b/endpoints/getuids.go index f420c64fa6b..dac114da227 100644 --- a/endpoints/getuids.go +++ b/endpoints/getuids.go @@ -4,8 +4,8 @@ import ( "net/http" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/usersync" "encoding/json" ) diff --git a/endpoints/getuids_test.go b/endpoints/getuids_test.go index 7988acbaffe..1092c51f79f 100644 --- a/endpoints/getuids_test.go +++ b/endpoints/getuids_test.go @@ -5,7 +5,7 @@ import ( "net/http/httptest" "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v3/config" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/info/bidders.go b/endpoints/info/bidders.go index bd6d078b3ba..163f6cee5bd 100644 --- a/endpoints/info/bidders.go +++ b/endpoints/info/bidders.go @@ -7,16 +7,16 @@ import ( "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) var invalidEnabledOnlyMsg = []byte(`Invalid value for 'enabledonly' query param, must be of boolean type`) var invalidBaseAdaptersOnlyMsg = []byte(`Invalid value for 'baseadaptersonly' query param, must be of boolean type`) // NewBiddersEndpoint builds a handler for the /info/bidders endpoint. -func NewBiddersEndpoint(bidders config.BidderInfos, aliases map[string]string) httprouter.Handle { - responseAll, err := prepareBiddersResponseAll(bidders, aliases) +func NewBiddersEndpoint(bidders config.BidderInfos) httprouter.Handle { + responseAll, err := prepareBiddersResponseAll(bidders) if err != nil { glog.Fatalf("error creating /info/bidders endpoint all bidders response: %v", err) } @@ -26,7 +26,7 @@ func NewBiddersEndpoint(bidders config.BidderInfos, aliases map[string]string) h glog.Fatalf("error creating /info/bidders endpoint all bidders (base adapters only) response: %v", err) } - responseEnabledOnly, err := prepareBiddersResponseEnabledOnly(bidders, aliases) + responseEnabledOnly, err := prepareBiddersResponseEnabledOnly(bidders) if err != nil { glog.Fatalf("error creating /info/bidders endpoint enabled only response: %v", err) } @@ -76,7 +76,6 @@ func readQueryFlag(r *http.Request, queryParam string) (flag, ok bool) { q := r.URL.Query() v, exists := q[queryParam] - if !exists || len(v) == 0 { return false, true } @@ -91,26 +90,13 @@ func readQueryFlag(r *http.Request, queryParam string) (flag, ok bool) { } } -func prepareBiddersResponseAll(bidders config.BidderInfos, aliases map[string]string) ([]byte, error) { - bidderNames := make([]string, 0, len(bidders)+len(aliases)) - - for name := range bidders { - bidderNames = append(bidderNames, name) - } - - for name := range aliases { - bidderNames = append(bidderNames, name) - } - - sort.Strings(bidderNames) - return jsonutil.Marshal(bidderNames) -} +type bidderPredicate func(config.BidderInfo) bool -func prepareBiddersResponseAllBaseOnly(bidders config.BidderInfos) ([]byte, error) { +func prepareResponse(bidders config.BidderInfos, p bidderPredicate) ([]byte, error) { bidderNames := make([]string, 0, len(bidders)) for name, info := range bidders { - if len(info.AliasOf) == 0 { + if p(info) { bidderNames = append(bidderNames, name) } } @@ -119,36 +105,24 @@ func prepareBiddersResponseAllBaseOnly(bidders config.BidderInfos) ([]byte, erro return jsonutil.Marshal(bidderNames) } -func prepareBiddersResponseEnabledOnly(bidders config.BidderInfos, aliases map[string]string) ([]byte, error) { - bidderNames := make([]string, 0, len(bidders)+len(aliases)) - - for name, info := range bidders { - if info.IsEnabled() { - bidderNames = append(bidderNames, name) - } - } +func prepareBiddersResponseAll(bidders config.BidderInfos) ([]byte, error) { + filterNone := func(_ config.BidderInfo) bool { return true } + return prepareResponse(bidders, filterNone) +} - for name, bidder := range aliases { - if info, ok := bidders[bidder]; ok && info.IsEnabled() { - bidderNames = append(bidderNames, name) - } - } +func prepareBiddersResponseAllBaseOnly(bidders config.BidderInfos) ([]byte, error) { + filterBaseOnly := func(info config.BidderInfo) bool { return len(info.AliasOf) == 0 } + return prepareResponse(bidders, filterBaseOnly) +} - sort.Strings(bidderNames) - return jsonutil.Marshal(bidderNames) +func prepareBiddersResponseEnabledOnly(bidders config.BidderInfos) ([]byte, error) { + filterEnabledOnly := func(info config.BidderInfo) bool { return info.IsEnabled() } + return prepareResponse(bidders, filterEnabledOnly) } func prepareBiddersResponseEnabledOnlyBaseOnly(bidders config.BidderInfos) ([]byte, error) { - bidderNames := make([]string, 0, len(bidders)) - - for name, info := range bidders { - if info.IsEnabled() && len(info.AliasOf) == 0 { - bidderNames = append(bidderNames, name) - } - } - - sort.Strings(bidderNames) - return jsonutil.Marshal(bidderNames) + filterEnabledOnlyBaseOnly := func(info config.BidderInfo) bool { return info.IsEnabled() && len(info.AliasOf) == 0 } + return prepareResponse(bidders, filterEnabledOnlyBaseOnly) } func writeBadRequest(w http.ResponseWriter, data []byte) { diff --git a/endpoints/info/bidders_detail.go b/endpoints/info/bidders_detail.go index 34d14efde1b..fb9ce252167 100644 --- a/endpoints/info/bidders_detail.go +++ b/endpoints/info/bidders_detail.go @@ -8,9 +8,9 @@ import ( "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) const ( @@ -19,20 +19,21 @@ const ( ) // NewBiddersDetailEndpoint builds a handler for the /info/bidders/ endpoint. -func NewBiddersDetailEndpoint(bidders config.BidderInfos, aliases map[string]string) httprouter.Handle { - responses, err := prepareBiddersDetailResponse(bidders, aliases) +func NewBiddersDetailEndpoint(bidders config.BidderInfos) httprouter.Handle { + responses, err := prepareBiddersDetailResponse(bidders) if err != nil { glog.Fatalf("error creating /info/bidders/ endpoint response: %v", err) } return func(w http.ResponseWriter, _ *http.Request, ps httprouter.Params) { bidder := ps.ByName("bidderName") + bidderName, found := getNormalizedBidderName(bidder) - coreBidderName, found := getNormalisedBidderName(bidder, aliases) if !found { w.WriteHeader(http.StatusNotFound) } - if response, ok := responses[coreBidderName]; ok { + + if response, ok := responses[bidderName]; ok { w.Header().Set("Content-Type", "application/json") if _, err := w.Write(response); err != nil { glog.Errorf("error writing response to /info/bidders/%s: %v", bidder, err) @@ -43,25 +44,21 @@ func NewBiddersDetailEndpoint(bidders config.BidderInfos, aliases map[string]str } } -func getNormalisedBidderName(bidderName string, aliases map[string]string) (string, bool) { +func getNormalizedBidderName(bidderName string) (string, bool) { if strings.ToLower(bidderName) == "all" { return "all", true } - coreBidderName, ok := openrtb_ext.NormalizeBidderName(bidderName) - if !ok { //check default aliases if not found in coreBidders - if _, isDefaultAlias := aliases[bidderName]; isDefaultAlias { - return bidderName, true - } + + bidderNameNormalized, ok := openrtb_ext.NormalizeBidderName(bidderName) + if !ok { return "", false } - return coreBidderName.String(), true + + return bidderNameNormalized.String(), true } -func prepareBiddersDetailResponse(bidders config.BidderInfos, aliases map[string]string) (map[string][]byte, error) { - details, err := mapDetails(bidders, aliases) - if err != nil { - return nil, err - } +func prepareBiddersDetailResponse(bidders config.BidderInfos) (map[string][]byte, error) { + details := mapDetails(bidders) responses, err := marshalDetailsResponse(details) if err != nil { @@ -77,25 +74,14 @@ func prepareBiddersDetailResponse(bidders config.BidderInfos, aliases map[string return responses, nil } -func mapDetails(bidders config.BidderInfos, aliases map[string]string) (map[string]bidderDetail, error) { +func mapDetails(bidders config.BidderInfos) map[string]bidderDetail { details := map[string]bidderDetail{} for bidderName, bidderInfo := range bidders { details[bidderName] = mapDetailFromConfig(bidderInfo) } - for aliasName, bidderName := range aliases { - aliasBaseInfo, aliasBaseInfoFound := details[bidderName] - if !aliasBaseInfoFound { - return nil, fmt.Errorf("base adapter %s for alias %s not found", bidderName, aliasName) - } - - aliasInfo := aliasBaseInfo - aliasInfo.AliasOf = bidderName - details[aliasName] = aliasInfo - } - - return details, nil + return details } func marshalDetailsResponse(details map[string]bidderDetail) (map[string][]byte, error) { @@ -151,6 +137,8 @@ type platform struct { func mapDetailFromConfig(c config.BidderInfo) bidderDetail { var bidderDetail bidderDetail + bidderDetail.AliasOf = c.AliasOf + if c.Maintainer != nil { bidderDetail.Maintainer = &maintainer{ Email: c.Maintainer.Email, diff --git a/endpoints/info/bidders_detail_test.go b/endpoints/info/bidders_detail_test.go index 435d0cec92c..f86a52692d9 100644 --- a/endpoints/info/bidders_detail_test.go +++ b/endpoints/info/bidders_detail_test.go @@ -9,8 +9,9 @@ import ( "testing" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -34,124 +35,72 @@ func TestPrepareBiddersDetailResponse(t *testing.T) { allResponseBidderAB.WriteString(`}`) var testCases = []struct { - description string + name string givenBidders config.BidderInfos - givenAliases map[string]string expectedResponses map[string][]byte - expectedError string }{ { - description: "None", + name: "none", givenBidders: config.BidderInfos{}, - givenAliases: map[string]string{}, expectedResponses: map[string][]byte{"all": []byte(`{}`)}, }, { - description: "One", + name: "one", givenBidders: config.BidderInfos{"a": bidderAInfo}, - givenAliases: map[string]string{}, expectedResponses: map[string][]byte{"a": bidderAResponse, "all": allResponseBidderA.Bytes()}, }, { - description: "Many", + name: "many", givenBidders: config.BidderInfos{"a": bidderAInfo, "b": bidderBInfo}, - givenAliases: map[string]string{}, expectedResponses: map[string][]byte{"a": bidderAResponse, "b": bidderBResponse, "all": allResponseBidderAB.Bytes()}, }, - { - description: "Error - Map Details", // Returns error due to invalid alias. - givenBidders: config.BidderInfos{"a": bidderAInfo}, - givenAliases: map[string]string{"zAlias": "z"}, - expectedError: "base adapter z for alias zAlias not found", - }, } for _, test := range testCases { - responses, err := prepareBiddersDetailResponse(test.givenBidders, test.givenAliases) - - if test.expectedError == "" { - assert.Equal(t, test.expectedResponses, responses, test.description+":responses") - assert.NoError(t, err, test.expectedError, test.description+":err") - } else { - assert.Empty(t, responses, test.description+":responses") - assert.EqualError(t, err, test.expectedError, test.description+":err") - } + t.Run(test.name, func(t *testing.T) { + responses, err := prepareBiddersDetailResponse(test.givenBidders) + assert.NoError(t, err) + assert.Equal(t, test.expectedResponses, responses) + }) } } func TestMapDetails(t *testing.T) { - trueValue := true - falseValue := false + var ( + bidderAInfo = config.BidderInfo{Endpoint: "https://secureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderA"}} + bidderADetail = bidderDetail{Status: "ACTIVE", UsesHTTPS: ptrutil.ToPtr(true), Maintainer: &maintainer{Email: "bidderA"}} - bidderAInfo := config.BidderInfo{Endpoint: "https://secureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderA"}} - bidderADetail := bidderDetail{Status: "ACTIVE", UsesHTTPS: &trueValue, Maintainer: &maintainer{Email: "bidderA"}} - aliasADetail := bidderDetail{Status: "ACTIVE", UsesHTTPS: &trueValue, Maintainer: &maintainer{Email: "bidderA"}, AliasOf: "a"} - - bidderBInfo := config.BidderInfo{Endpoint: "http://unsecureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderB"}} - bidderBDetail := bidderDetail{Status: "ACTIVE", UsesHTTPS: &falseValue, Maintainer: &maintainer{Email: "bidderB"}} - aliasBDetail := bidderDetail{Status: "ACTIVE", UsesHTTPS: &falseValue, Maintainer: &maintainer{Email: "bidderB"}, AliasOf: "b"} + bidderBInfo = config.BidderInfo{Endpoint: "http://unsecureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderB"}} + bidderBDetail = bidderDetail{Status: "ACTIVE", UsesHTTPS: ptrutil.ToPtr(false), Maintainer: &maintainer{Email: "bidderB"}} + ) var testCases = []struct { - description string + name string givenBidders config.BidderInfos - givenAliases map[string]string expectedDetails map[string]bidderDetail - expectedError string }{ { - description: "None", + name: "none", givenBidders: config.BidderInfos{}, - givenAliases: map[string]string{}, expectedDetails: map[string]bidderDetail{}, }, { - description: "One Core Bidder", + name: "one", givenBidders: config.BidderInfos{"a": bidderAInfo}, - givenAliases: map[string]string{}, expectedDetails: map[string]bidderDetail{"a": bidderADetail}, }, { - description: "Many Core Bidders", + name: "many", givenBidders: config.BidderInfos{"a": bidderAInfo, "b": bidderBInfo}, - givenAliases: map[string]string{}, expectedDetails: map[string]bidderDetail{"a": bidderADetail, "b": bidderBDetail}, }, - { - description: "One Alias", - givenBidders: config.BidderInfos{"a": bidderAInfo}, - givenAliases: map[string]string{"aAlias": "a"}, - expectedDetails: map[string]bidderDetail{"a": bidderADetail, "aAlias": aliasADetail}, - }, - { - description: "Many Aliases - Same Core Bidder", - givenBidders: config.BidderInfos{"a": bidderAInfo}, - givenAliases: map[string]string{"aAlias1": "a", "aAlias2": "a"}, - expectedDetails: map[string]bidderDetail{"a": bidderADetail, "aAlias1": aliasADetail, "aAlias2": aliasADetail}, - }, - { - description: "Many Aliases - Different Core Bidders", - givenBidders: config.BidderInfos{"a": bidderAInfo, "b": bidderBInfo}, - givenAliases: map[string]string{"aAlias": "a", "bAlias": "b"}, - expectedDetails: map[string]bidderDetail{"a": bidderADetail, "b": bidderBDetail, "aAlias": aliasADetail, "bAlias": aliasBDetail}, - }, - { - description: "Error - Alias Without Core Bidder", - givenBidders: config.BidderInfos{"a": bidderAInfo}, - givenAliases: map[string]string{"zAlias": "z"}, - expectedError: "base adapter z for alias zAlias not found", - }, } for _, test := range testCases { - details, err := mapDetails(test.givenBidders, test.givenAliases) - - if test.expectedError == "" { - assert.Equal(t, test.expectedDetails, details, test.description+":details") - assert.NoError(t, err, test.expectedError, test.description+":err") - } else { - assert.Empty(t, details, test.description+":details") - assert.EqualError(t, err, test.expectedError, test.description+":err") - } + t.Run(test.name, func(t *testing.T) { + details := mapDetails(test.givenBidders) + assert.Equal(t, test.expectedDetails, details) + }) } } @@ -165,32 +114,33 @@ func TestMarshalDetailsResponse(t *testing.T) { bidderDetailBResponse := []byte(`{"status":"ACTIVE","maintainer":{"email":"bidderB"},"capabilities":{"app":{"mediaTypes":["banner"]}}}`) var testCases = []struct { - description string + name string givenDetails map[string]bidderDetail expectedResponse map[string][]byte }{ { - description: "None", + name: "none", givenDetails: map[string]bidderDetail{}, expectedResponse: map[string][]byte{}, }, { - description: "One", + name: "one", givenDetails: map[string]bidderDetail{"a": bidderDetailA}, expectedResponse: map[string][]byte{"a": bidderDetailAResponse}, }, { - description: "Many", + name: "many", givenDetails: map[string]bidderDetail{"a": bidderDetailA, "b": bidderDetailB}, expectedResponse: map[string][]byte{"a": bidderDetailAResponse, "b": bidderDetailBResponse}, }, } for _, test := range testCases { - response, err := marshalDetailsResponse(test.givenDetails) - - assert.NoError(t, err, test.description+":err") - assert.Equal(t, test.expectedResponse, response, test.description+":response") + t.Run(test.name, func(t *testing.T) { + response, err := marshalDetailsResponse(test.givenDetails) + assert.NoError(t, err) + assert.Equal(t, test.expectedResponse, response) + }) } } @@ -211,12 +161,12 @@ func TestMapDetailFromConfig(t *testing.T) { falseValue := false var testCases = []struct { - description string + name string givenBidderInfo config.BidderInfo expected bidderDetail }{ { - description: "Enabled - All Values Present", + name: "enabled-all-values", givenBidderInfo: config.BidderInfo{ Endpoint: "http://anyEndpoint", Disabled: false, @@ -244,7 +194,7 @@ func TestMapDetailFromConfig(t *testing.T) { }, }, { - description: "Disabled - All Values Present", + name: "disabled-all-values", givenBidderInfo: config.BidderInfo{ Endpoint: "http://anyEndpoint", Disabled: true, @@ -267,7 +217,7 @@ func TestMapDetailFromConfig(t *testing.T) { }, }, { - description: "Enabled - No Values Present", + name: "enabled-no-values", givenBidderInfo: config.BidderInfo{ Endpoint: "http://amyEndpoint", Disabled: false, @@ -278,7 +228,7 @@ func TestMapDetailFromConfig(t *testing.T) { }, }, { - description: "Enabled - Protocol - HTTP", + name: "enabled-protocol-http", givenBidderInfo: config.BidderInfo{ Endpoint: "http://amyEndpoint", Disabled: false, @@ -289,7 +239,7 @@ func TestMapDetailFromConfig(t *testing.T) { }, }, { - description: "Enabled - Protocol - HTTPS", + name: "enabled-protocol-https", givenBidderInfo: config.BidderInfo{ Endpoint: "https://amyEndpoint", Disabled: false, @@ -300,7 +250,7 @@ func TestMapDetailFromConfig(t *testing.T) { }, }, { - description: "Enabled - Protocol - HTTPS - Case Insensitive", + name: "enabled-protocol-https-case-insensitive", givenBidderInfo: config.BidderInfo{ Disabled: false, Endpoint: "https://amyEndpoint", @@ -311,7 +261,7 @@ func TestMapDetailFromConfig(t *testing.T) { }, }, { - description: "Enabled - Protocol - Unknown", + name: "enabled-protocol-unknown", givenBidderInfo: config.BidderInfo{ Endpoint: "endpointWithoutProtocol", Disabled: false, @@ -324,125 +274,132 @@ func TestMapDetailFromConfig(t *testing.T) { } for _, test := range testCases { - result := mapDetailFromConfig(test.givenBidderInfo) - assert.Equal(t, test.expected, result, test.description) + t.Run(test.name, func(t *testing.T) { + result := mapDetailFromConfig(test.givenBidderInfo) + assert.Equal(t, test.expected, result) + }) } } func TestMapMediaTypes(t *testing.T) { var testCases = []struct { - description string - mediaTypes []openrtb_ext.BidType - expected []string + name string + mediaTypes []openrtb_ext.BidType + expected []string }{ { - description: "Nil", - mediaTypes: nil, - expected: nil, + name: "nil", + mediaTypes: nil, + expected: nil, }, { - description: "None", - mediaTypes: []openrtb_ext.BidType{}, - expected: []string{}, + name: "none", + mediaTypes: []openrtb_ext.BidType{}, + expected: []string{}, }, { - description: "One", - mediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, - expected: []string{"banner"}, + name: "one", + mediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, + expected: []string{"banner"}, }, { - description: "Many", - mediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo}, - expected: []string{"banner", "video"}, + name: "many", + mediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo}, + expected: []string{"banner", "video"}, }, } for _, test := range testCases { - result := mapMediaTypes(test.mediaTypes) - assert.ElementsMatch(t, test.expected, result, test.description) + t.Run(test.name, func(t *testing.T) { + result := mapMediaTypes(test.mediaTypes) + assert.ElementsMatch(t, test.expected, result) + }) } } func TestBiddersDetailHandler(t *testing.T) { bidderAInfo := config.BidderInfo{Endpoint: "https://secureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderA"}} bidderAResponse := []byte(`{"status":"ACTIVE","usesHttps":true,"maintainer":{"email":"bidderA"}}`) - aliasAResponse := []byte(`{"status":"ACTIVE","usesHttps":true,"maintainer":{"email":"bidderA"},"aliasOf":"appnexus"}`) bidderBInfo := config.BidderInfo{Endpoint: "http://unsecureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderB"}} bidderBResponse := []byte(`{"status":"ACTIVE","usesHttps":false,"maintainer":{"email":"bidderB"}}`) + aliasInfo := config.BidderInfo{AliasOf: "appnexus", Endpoint: "https://secureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "alias"}} + aliasResponse := []byte(`{"status":"ACTIVE","usesHttps":true,"maintainer":{"email":"alias"},"aliasOf":"appnexus"}`) + allResponse := bytes.Buffer{} allResponse.WriteString(`{"aAlias":`) - allResponse.Write(aliasAResponse) + allResponse.Write(aliasResponse) allResponse.WriteString(`,"appnexus":`) allResponse.Write(bidderAResponse) allResponse.WriteString(`,"rubicon":`) allResponse.Write(bidderBResponse) allResponse.WriteString(`}`) - bidders := config.BidderInfos{"appnexus": bidderAInfo, "rubicon": bidderBInfo} - aliases := map[string]string{"aAlias": "appnexus"} + bidders := config.BidderInfos{"aAlias": aliasInfo, "appnexus": bidderAInfo, "rubicon": bidderBInfo} + + handler := NewBiddersDetailEndpoint(bidders) - handler := NewBiddersDetailEndpoint(bidders, aliases) + openrtb_ext.SetAliasBidderName("aAlias", "appnexus") var testCases = []struct { - description string + name string givenBidder string expectedStatus int expectedHeaders http.Header expectedResponse []byte }{ { - description: "Bidder A", + name: "bidder-a", givenBidder: "appnexus", expectedStatus: http.StatusOK, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, expectedResponse: bidderAResponse, }, { - description: "Bidder B", + name: "bidder-b", givenBidder: "rubicon", expectedStatus: http.StatusOK, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, expectedResponse: bidderBResponse, }, { - description: "Bidder B - case insensitive", + name: "bidder-b-case-insensitive", givenBidder: "RUBICON", expectedStatus: http.StatusOK, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, expectedResponse: bidderBResponse, }, { - description: "Bidder A Alias", + name: "bidder-a-alias", givenBidder: "aAlias", expectedStatus: http.StatusOK, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, - expectedResponse: aliasAResponse, + expectedResponse: aliasResponse, }, { - description: "Bidder A Alias - case insensitive", + name: "bidder-a-alias-case-insensitive", givenBidder: "aAlias", expectedStatus: http.StatusOK, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, - expectedResponse: aliasAResponse, + expectedResponse: aliasResponse, }, { - description: "All Bidders", + name: "all-bidders", givenBidder: "all", expectedStatus: http.StatusOK, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, expectedResponse: allResponse.Bytes(), }, { - description: "All Bidders - Case insensitive", + name: "all-bidders-case-insensitive", givenBidder: "All", expectedStatus: http.StatusOK, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, expectedResponse: allResponse.Bytes(), }, { - description: "Invalid Bidder", + name: "invalid", givenBidder: "doesntExist", expectedStatus: http.StatusNotFound, expectedHeaders: http.Header{}, @@ -451,19 +408,22 @@ func TestBiddersDetailHandler(t *testing.T) { } for _, test := range testCases { - t.Run(test.description, func(t *testing.T) { + t.Run(test.name, func(t *testing.T) { responseRecorder := httptest.NewRecorder() - handler(responseRecorder, nil, httprouter.Params{{"bidderName", test.givenBidder}}) + handler(responseRecorder, nil, httprouter.Params{{ + Key: "bidderName", + Value: test.givenBidder, + }}) result := responseRecorder.Result() - assert.Equal(t, result.StatusCode, test.expectedStatus, test.description+":statuscode") + assert.Equal(t, test.expectedStatus, result.StatusCode, "statuscode") resultBody, _ := io.ReadAll(result.Body) fmt.Println(string(test.expectedResponse)) - assert.Equal(t, test.expectedResponse, resultBody, test.description+":body") + assert.Equal(t, test.expectedResponse, resultBody, "body") resultHeaders := result.Header - assert.Equal(t, test.expectedHeaders, resultHeaders, test.description+":headers") + assert.Equal(t, test.expectedHeaders, resultHeaders, "headers") }) } } diff --git a/endpoints/info/bidders_test.go b/endpoints/info/bidders_test.go index 1f483e5de27..b75b32aae10 100644 --- a/endpoints/info/bidders_test.go +++ b/endpoints/info/bidders_test.go @@ -6,7 +6,7 @@ import ( "net/http/httptest" "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v3/config" "github.com/stretchr/testify/assert" ) @@ -19,88 +19,65 @@ func TestPrepareBiddersResponseAll(t *testing.T) { ) testCases := []struct { - name string - givenBidders config.BidderInfos - givenRequestAliases map[string]string - expected string + name string + givenBidders config.BidderInfos + expected string }{ { - name: "none", - givenBidders: config.BidderInfos{}, - givenRequestAliases: nil, - expected: `[]`, - }, - { - name: "core-one-enabled", - givenBidders: config.BidderInfos{"a": enabledCore}, - givenRequestAliases: nil, - expected: `["a"]`, - }, - { - name: "core-one-disabled", - givenBidders: config.BidderInfos{"a": disabledCore}, - givenRequestAliases: nil, - expected: `["a"]`, + name: "none", + givenBidders: config.BidderInfos{}, + expected: `[]`, }, { - name: "core-one-mixed", - givenBidders: config.BidderInfos{"a": disabledCore, "b": enabledCore}, - givenRequestAliases: nil, - expected: `["a","b"]`, + name: "core-one-enabled", + givenBidders: config.BidderInfos{"a": enabledCore}, + expected: `["a"]`, }, { - name: "core-one-mixed-sorted", - givenBidders: config.BidderInfos{"z": enabledCore, "a": enabledCore}, - givenRequestAliases: nil, - expected: `["a","z"]`, + name: "core-one-disabled", + givenBidders: config.BidderInfos{"a": disabledCore}, + expected: `["a"]`, }, { - name: "alias-one", - givenBidders: config.BidderInfos{"a": enabledAlias}, - givenRequestAliases: nil, - expected: `["a"]`, + name: "core-many-mixed", + givenBidders: config.BidderInfos{"a": disabledCore, "b": enabledCore}, + expected: `["a","b"]`, }, { - name: "alias-mixed", - givenBidders: config.BidderInfos{"a": disabledCore, "b": disabledAlias, "c": enabledCore, "d": enabledAlias}, - givenRequestAliases: nil, - expected: `["a","b","c","d"]`, + name: "core-many-sorted", + givenBidders: config.BidderInfos{"z": enabledCore, "a": enabledCore}, + expected: `["a","z"]`, }, { - name: "alias-mixed-sorted", - givenBidders: config.BidderInfos{"z": enabledAlias, "a": enabledCore}, - givenRequestAliases: nil, - expected: `["a","z"]`, + name: "alias-one-enabled", + givenBidders: config.BidderInfos{"a": enabledAlias}, + expected: `["a"]`, }, { - name: "defaultrequest-one", - givenBidders: config.BidderInfos{"a": enabledCore}, - givenRequestAliases: map[string]string{"b": "a"}, - expected: `["a","b"]`, + name: "alias-one-disabled", + givenBidders: config.BidderInfos{"a": disabledAlias}, + expected: `["a"]`, }, { - name: "defaultrequest-mixed", - givenBidders: config.BidderInfos{"a": enabledCore, "b": disabledCore}, - givenRequestAliases: map[string]string{"x": "a", "y": "b"}, - expected: `["a","b","x","y"]`, + name: "alias-many-mixed", + givenBidders: config.BidderInfos{"a": enabledAlias, "b": disabledAlias}, + expected: `["a","b"]`, }, { - name: "defaultrequest-mixed-sorted", - givenBidders: config.BidderInfos{"z": enabledCore}, - givenRequestAliases: map[string]string{"a": "z"}, - expected: `["a","z"]`, + name: "alias-many-sorted", + givenBidders: config.BidderInfos{"z": enabledAlias, "a": enabledCore}, + expected: `["a","z"]`, }, { - name: "mixed", - givenBidders: config.BidderInfos{"a": disabledCore, "b": disabledAlias, "c": enabledCore, "d": enabledAlias}, - givenRequestAliases: map[string]string{"z": "a"}, - expected: `["a","b","c","d","z"]`, + name: "mixed", + givenBidders: config.BidderInfos{"a": disabledCore, "b": disabledAlias, "c": enabledCore, "d": enabledAlias}, + expected: `["a","b","c","d"]`, }, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - result, err := prepareBiddersResponseAll(test.givenBidders, test.givenRequestAliases) + result, err := prepareBiddersResponseAll(test.givenBidders) assert.NoError(t, err) assert.Equal(t, []byte(test.expected), result) }) @@ -136,22 +113,32 @@ func TestPrepareBiddersResponseAllBaseOnly(t *testing.T) { expected: `["a"]`, }, { - name: "core-one-mixed", + name: "core-many-mixed", givenBidders: config.BidderInfos{"a": disabledCore, "b": enabledCore}, expected: `["a","b"]`, }, { - name: "core-one-mixed-sorted", + name: "core-many-sorted", givenBidders: config.BidderInfos{"z": enabledCore, "a": enabledCore}, expected: `["a","z"]`, }, { - name: "alias-one", + name: "alias-one-enabled", givenBidders: config.BidderInfos{"a": enabledAlias}, expected: `[]`, }, { - name: "alias-mixed", + name: "alias-one-disabled", + givenBidders: config.BidderInfos{"a": disabledAlias}, + expected: `[]`, + }, + { + name: "alias-many", + givenBidders: config.BidderInfos{"a": enabledAlias, "b": enabledAlias}, + expected: `[]`, + }, + { + name: "mixed", givenBidders: config.BidderInfos{"a": disabledCore, "b": disabledAlias, "c": enabledCore, "d": enabledAlias}, expected: `["a","c"]`, }, @@ -175,88 +162,65 @@ func TestPrepareBiddersResponseEnabledOnly(t *testing.T) { ) testCases := []struct { - name string - givenBidders config.BidderInfos - givenRequestAliases map[string]string - expected string + name string + givenBidders config.BidderInfos + expected string }{ { - name: "none", - givenBidders: config.BidderInfos{}, - givenRequestAliases: nil, - expected: `[]`, - }, - { - name: "core-one-enabled", - givenBidders: config.BidderInfos{"a": enabledCore}, - givenRequestAliases: nil, - expected: `["a"]`, - }, - { - name: "core-one-disabled", - givenBidders: config.BidderInfos{"a": disabledCore}, - givenRequestAliases: nil, - expected: `[]`, + name: "none", + givenBidders: config.BidderInfos{}, + expected: `[]`, }, { - name: "core-one-mixed", - givenBidders: config.BidderInfos{"a": disabledCore, "b": enabledCore}, - givenRequestAliases: nil, - expected: `["b"]`, + name: "core-one-enabled", + givenBidders: config.BidderInfos{"a": enabledCore}, + expected: `["a"]`, }, { - name: "core-one-mixed-sorted", - givenBidders: config.BidderInfos{"z": enabledCore, "a": enabledCore}, - givenRequestAliases: nil, - expected: `["a","z"]`, + name: "core-one-disabled", + givenBidders: config.BidderInfos{"a": disabledCore}, + expected: `[]`, }, { - name: "alias-one", - givenBidders: config.BidderInfos{"a": enabledAlias}, - givenRequestAliases: nil, - expected: `["a"]`, + name: "core-many-mixed", + givenBidders: config.BidderInfos{"a": disabledCore, "b": enabledCore}, + expected: `["b"]`, }, { - name: "alias-mixed", - givenBidders: config.BidderInfos{"a": disabledCore, "b": disabledAlias, "c": enabledCore, "d": enabledAlias}, - givenRequestAliases: nil, - expected: `["c","d"]`, + name: "core-many-sorted", + givenBidders: config.BidderInfos{"z": enabledCore, "a": enabledCore}, + expected: `["a","z"]`, }, { - name: "alias-mixed-sorted", - givenBidders: config.BidderInfos{"z": enabledAlias, "a": enabledCore}, - givenRequestAliases: nil, - expected: `["a","z"]`, + name: "alias-one-enabled", + givenBidders: config.BidderInfos{"a": enabledAlias}, + expected: `["a"]`, }, { - name: "defaultrequest-one", - givenBidders: config.BidderInfos{"a": enabledCore}, - givenRequestAliases: map[string]string{"b": "a"}, - expected: `["a","b"]`, + name: "alias-one-disabled", + givenBidders: config.BidderInfos{"a": disabledAlias}, + expected: `[]`, }, { - name: "defaultrequest-mixed", - givenBidders: config.BidderInfos{"a": enabledCore, "b": disabledCore}, - givenRequestAliases: map[string]string{"x": "a", "y": "b"}, - expected: `["a","x"]`, + name: "alias-many-mixed", + givenBidders: config.BidderInfos{"a": enabledAlias, "b": disabledAlias}, + expected: `["a"]`, }, { - name: "defaultrequest-mixed-sorted", - givenBidders: config.BidderInfos{"z": enabledCore}, - givenRequestAliases: map[string]string{"a": "z"}, - expected: `["a","z"]`, + name: "alias-many-sorted", + givenBidders: config.BidderInfos{"z": enabledAlias, "a": enabledCore}, + expected: `["a","z"]`, }, { - name: "mixed", - givenBidders: config.BidderInfos{"a": disabledCore, "b": disabledAlias, "c": enabledCore, "d": enabledAlias}, - givenRequestAliases: map[string]string{"z": "a"}, - expected: `["c","d"]`, + name: "mixed", + givenBidders: config.BidderInfos{"a": disabledCore, "b": disabledAlias, "c": enabledCore, "d": enabledAlias}, + expected: `["c","d"]`, }, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - result, err := prepareBiddersResponseEnabledOnly(test.givenBidders, test.givenRequestAliases) + result, err := prepareBiddersResponseEnabledOnly(test.givenBidders) assert.NoError(t, err) assert.Equal(t, []byte(test.expected), result) }) @@ -292,17 +256,27 @@ func TestPrepareBiddersResponseEnabledOnlyBaseOnly(t *testing.T) { expected: `[]`, }, { - name: "core-one-mixed", + name: "core-many", + givenBidders: config.BidderInfos{"a": enabledCore, "b": enabledCore}, + expected: `["a","b"]`, + }, + { + name: "core-many-mixed", givenBidders: config.BidderInfos{"a": disabledCore, "b": enabledCore}, expected: `["b"]`, }, { - name: "core-one-mixed-sorted", + name: "core-many-sorted", givenBidders: config.BidderInfos{"z": enabledCore, "a": enabledCore}, expected: `["a","z"]`, }, { - name: "alias-one", + name: "alias-one-enabled", + givenBidders: config.BidderInfos{"a": enabledAlias}, + expected: `[]`, + }, + { + name: "alias-one-disabled", givenBidders: config.BidderInfos{"a": enabledAlias}, expected: `[]`, }, @@ -336,7 +310,6 @@ func TestBiddersHandler(t *testing.T) { ) bidders := config.BidderInfos{"a": enabledCore, "b": enabledAlias, "c": disabledCore, "d": disabledAlias} - aliases := map[string]string{"x": "a", "y": "c"} testCases := []struct { name string @@ -349,35 +322,35 @@ func TestBiddersHandler(t *testing.T) { name: "simple", givenURL: "/info/bidders", expectedStatus: http.StatusOK, - expectedBody: `["a","b","c","d","x","y"]`, + expectedBody: `["a","b","c","d"]`, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, }, { name: "enabledonly-false", givenURL: "/info/bidders?enabledonly=false", expectedStatus: http.StatusOK, - expectedBody: `["a","b","c","d","x","y"]`, + expectedBody: `["a","b","c","d"]`, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, }, { name: "enabledonly-false-caseinsensitive", givenURL: "/info/bidders?enabledonly=fAlSe", expectedStatus: http.StatusOK, - expectedBody: `["a","b","c","d","x","y"]`, + expectedBody: `["a","b","c","d"]`, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, }, { name: "enabledonly-true", givenURL: "/info/bidders?enabledonly=true", expectedStatus: http.StatusOK, - expectedBody: `["a","b","x"]`, + expectedBody: `["a","b"]`, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, }, { name: "enabledonly-true-caseinsensitive", givenURL: "/info/bidders?enabledonly=TrUe", expectedStatus: http.StatusOK, - expectedBody: `["a","b","x"]`, + expectedBody: `["a","b"]`, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, }, { @@ -398,14 +371,14 @@ func TestBiddersHandler(t *testing.T) { name: "baseonly-false", givenURL: "/info/bidders?baseadaptersonly=false", expectedStatus: http.StatusOK, - expectedBody: `["a","b","c","d","x","y"]`, + expectedBody: `["a","b","c","d"]`, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, }, { name: "baseonly-false-caseinsensitive", givenURL: "/info/bidders?baseadaptersonly=fAlSe", expectedStatus: http.StatusOK, - expectedBody: `["a","b","c","d","x","y"]`, + expectedBody: `["a","b","c","d"]`, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, }, { @@ -440,7 +413,7 @@ func TestBiddersHandler(t *testing.T) { name: "enabledonly-true-baseonly-false", givenURL: "/info/bidders?enabledonly=true&baseadaptersonly=false", expectedStatus: http.StatusOK, - expectedBody: `["a","b","x"]`, + expectedBody: `["a","b"]`, expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, }, { @@ -461,7 +434,7 @@ func TestBiddersHandler(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - handler := NewBiddersEndpoint(bidders, aliases) + handler := NewBiddersEndpoint(bidders) request := httptest.NewRequest("GET", test.givenURL, nil) diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 76b0e83cf3d..b596ec793c6 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -14,31 +14,31 @@ import ( "github.com/buger/jsonparser" "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/openrtb/v19/openrtb3" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/ortb" - "github.com/prebid/prebid-server/util/uuidutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/openrtb/v20/openrtb3" + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/ortb" + "github.com/prebid/prebid-server/v3/util/uuidutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/amp" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/stored_responses" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/version" + accountService "github.com/prebid/prebid-server/v3/account" + "github.com/prebid/prebid-server/v3/amp" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/exchange" + "github.com/prebid/prebid-server/v3/gdpr" + "github.com/prebid/prebid-server/v3/hooks" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/privacy" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v3/stored_responses" + "github.com/prebid/prebid-server/v3/usersync" + "github.com/prebid/prebid-server/v3/util/iputil" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/version" ) const defaultAmpRequestTimeoutMillis = 900 @@ -59,7 +59,7 @@ type ORTB2 struct { func NewAmpEndpoint( uuidGenerator uuidutil.UUIDGenerator, ex exchange.Exchange, - validator openrtb_ext.BidderParamValidator, + requestValidator ortb.RequestValidator, requestsById stored_requests.Fetcher, accounts stored_requests.AccountFetcher, cfg *config.Configuration, @@ -73,7 +73,7 @@ func NewAmpEndpoint( tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed, ) (httprouter.Handle, error) { - if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || metricsEngine == nil { + if ex == nil || requestValidator == nil || requestsById == nil || accounts == nil || cfg == nil || metricsEngine == nil { return nil, errors.New("NewAmpEndpoint requires non-nil arguments.") } @@ -87,7 +87,7 @@ func NewAmpEndpoint( return httprouter.Handle((&endpointDeps{ uuidGenerator, ex, - validator, + requestValidator, requestsById, empty_fetcher.EmptyFetcher{}, accounts, @@ -156,6 +156,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h w.Header().Set("AMP-Access-Control-Allow-Source-Origin", origin) w.Header().Set("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin") w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver)) + setBrowsingTopicsHeader(w, r) // There is no body for AMP requests, so we pass a nil body and ignore the return value. _, rejectErr := hookExecutor.ExecuteEntrypointStage(r, nilBody) @@ -171,7 +172,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h if errortypes.ContainsFatalError(errL) { w.WriteHeader(http.StatusBadRequest) for _, err := range errortypes.FatalOnly(errL) { - w.Write([]byte(fmt.Sprintf("Invalid request: %s\n", err.Error()))) + fmt.Fprintf(w, "Invalid request: %s\n", err.Error()) } labels.RequestStatus = metrics.RequestStatusBadInput return @@ -210,9 +211,9 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h metricsStatus := metrics.RequestStatusBadInput for _, er := range errL { errCode := errortypes.ReadCode(er) - if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.AccountDisabledErrorCode { + if errCode == errortypes.BlockedAppErrorCode || errCode == errortypes.AccountDisabledErrorCode { httpStatus = http.StatusServiceUnavailable - metricsStatus = metrics.RequestStatusBlacklisted + metricsStatus = metrics.RequestStatusBlockedApp break } if errCode == errortypes.MalformedAcctErrorCode { @@ -224,16 +225,37 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h w.WriteHeader(httpStatus) labels.RequestStatus = metricsStatus for _, err := range errortypes.FatalOnly(errL) { - w.Write([]byte(fmt.Sprintf("Invalid request: %s\n", err.Error()))) + fmt.Fprintf(w, "Invalid request: %s\n", err.Error()) } ao.Errors = append(ao.Errors, acctIDErrs...) return } + // Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers). + if errs := deps.setFieldsImplicitly(r, reqWrapper, account); len(errs) > 0 { + errL = append(errL, errs...) + } + + hasStoredAuctionResponses := len(storedAuctionResponses) > 0 + errs := deps.validateRequest(account, r, reqWrapper, true, hasStoredAuctionResponses, storedBidResponses, false) + errL = append(errL, errs...) + ao.Errors = append(ao.Errors, errs...) + if errortypes.ContainsFatalError(errs) { + w.WriteHeader(http.StatusBadRequest) + for _, err := range errortypes.FatalOnly(errs) { + fmt.Fprintf(w, "Invalid request: %s\n", err.Error()) + } + labels.RequestStatus = metrics.RequestStatusBadInput + return + } + tcf2Config := gdpr.NewTCF2Config(deps.cfg.GDPR.TCF2, account.GDPR) activityControl = privacy.NewActivityControl(&account.Privacy) + hookExecutor.SetActivityControl(activityControl) + hookExecutor.SetAccount(account) + secGPC := r.Header.Get("Sec-GPC") auctionRequest := &exchange.AuctionRequest{ @@ -382,8 +404,13 @@ func sendAmpResponse( ao.AmpTargetingValues = targets // Fixes #231 - enc := json.NewEncoder(w) + enc := json.NewEncoder(w) // nosemgrep: json-encoder-needs-type enc.SetEscapeHTML(false) + // Explicitly set content type to text/plain, which had previously been + // the implied behavior from the time the project was launched. + // It's unclear why text/plain was chosen or if it was an oversight, + // nevertheless we will keep it as such for compatibility reasons. + w.Header().Set("Content-Type", "text/plain; charset=utf-8") // If an error happens when encoding the response, there isn't much we can do. // If we've sent _any_ bytes, then Go would have sent the 200 status code first. @@ -420,6 +447,9 @@ func getExtBidResponse( warnings = make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage) } for _, v := range errortypes.WarningOnly(errs) { + if errortypes.ReadScope(v) == errortypes.ScopeDebug && !(reqWrapper != nil && reqWrapper.Test == 1) { + continue + } bidderErr := openrtb_ext.ExtBidderMessage{ Code: errortypes.ReadCode(v), Message: v.Error(), @@ -480,8 +510,13 @@ func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openr // move to using the request wrapper req = &openrtb_ext.RequestWrapper{BidRequest: reqNormal} - // Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers). - deps.setFieldsImplicitly(httpRequest, req) + // normalize to openrtb 2.6 + if err := openrtb_ext.ConvertUpTo26(req); err != nil { + errs = append(errs, err) + } + if errortypes.ContainsFatalError(errs) { + return + } // Need to ensure cache and targeting are turned on e = initAmpTargetingAndCache(req) @@ -494,10 +529,6 @@ func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openr return } - hasStoredResponses := len(storedAuctionResponses) > 0 - e = deps.validateRequest(req, true, hasStoredResponses, storedBidResponses, false) - errs = append(errs, e...) - return } @@ -511,7 +542,7 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req return nil, nil, nil, nil, []error{err} } - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(storedRequestTimeoutMillis)*time.Millisecond) + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(deps.cfg.StoredRequestsTimeout)*time.Millisecond) defer cancel() storedRequests, _, errs := deps.storedReqFetcher.FetchRequests(ctx, []string{ampParams.StoredRequestID}, nil) @@ -530,7 +561,7 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req return } - storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, errs = stored_responses.ProcessStoredResponses(ctx, requestJSON, deps.storedRespFetcher, deps.bidderMap) + storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, errs = stored_responses.ProcessStoredResponses(ctx, &openrtb_ext.RequestWrapper{BidRequest: req}, deps.storedRespFetcher) if err != nil { errs = []error{err} return diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 7de8995754a..3fe8a629f02 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -10,29 +10,31 @@ import ( "net/url" "os" "strconv" + "strings" "testing" "time" "github.com/julienschmidt/httprouter" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/prebid/prebid-server/amp" - "github.com/prebid/prebid-server/analytics" - analyticsBuild "github.com/prebid/prebid-server/analytics/build" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/metrics" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/amp" + "github.com/prebid/prebid-server/v3/analytics" + analyticsBuild "github.com/prebid/prebid-server/v3/analytics/build" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/exchange" + "github.com/prebid/prebid-server/v3/hooks" + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/hooks/hookstage" + "github.com/prebid/prebid-server/v3/metrics" + metricsConfig "github.com/prebid/prebid-server/v3/metrics/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/ortb" + "github.com/prebid/prebid-server/v3/privacy" + "github.com/prebid/prebid-server/v3/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) // TestGoodRequests makes sure that the auction runs properly-formatted stored bids correctly. @@ -54,6 +56,7 @@ func TestGoodAmpRequests(t *testing.T) { "buyeruids-case-insensitive.json", "buyeruids-camel-case.json", "aliased-buyeruids-case-insensitive.json", + "ortb-2.5-to-2.6-upconvert.json", }, }, { @@ -101,8 +104,8 @@ func TestGoodAmpRequests(t *testing.T) { GDPR: config.GDPR{Enabled: true}, } if test.Config != nil { - cfg.BlacklistedApps = test.Config.BlacklistedApps - cfg.BlacklistedAppMap = test.Config.getBlacklistedAppMap() + cfg.BlockedApps = test.Config.BlockedApps + cfg.BlockedAppsLookup = test.Config.getBlockedAppLookup() cfg.AccountRequired = test.Config.AccountRequired } @@ -136,6 +139,17 @@ func TestGoodAmpRequests(t *testing.T) { assert.JSONEq(t, string(test.ExpectedValidatedBidReq), string(actualJson), "Not the expected validated request. Test file: %s", filename) } } + if test.ExpectedMockBidderRequests != nil { + for bidder, req := range test.ExpectedMockBidderRequests { + a, ok := ex.adapters[openrtb_ext.BidderName(bidder)] + if !ok { + t.Fatalf("Unexpected bidder %s has an expected mock bidder request. Test file: %s", bidder, filename) + } + aa := a.(*exchange.BidderAdapter) + ma := aa.Bidder.(*mockAdapter) + assert.JSONEq(t, string(req), string(ma.requestData[0]), "Not the expected mock bidder request for bidder %s. Test file: %s", bidder, filename) + } + } } } } @@ -201,7 +215,7 @@ func TestAMPPageInfo(t *testing.T) { endpoint, _ := NewAmpEndpoint( fakeUUIDGenerator{}, exchange, - newParamsValidator(t), + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)), &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, @@ -233,55 +247,47 @@ func TestGDPRConsent(t *testing.T) { existingConsent := "BONV8oqONXwgmADACHENAO7pqzAAppY" testCases := []struct { - description string - consent string - userExt *openrtb_ext.ExtUser - nilUser bool - expectedUserExt openrtb_ext.ExtUser + description string + consent string + user *openrtb2.User + nilUser bool + expectedUser *openrtb2.User }{ { description: "Nil User", consent: consent, nilUser: true, - expectedUserExt: openrtb_ext.ExtUser{ - Consent: consent, - }, - }, - { - description: "Nil User Ext", - consent: consent, - userExt: nil, - expectedUserExt: openrtb_ext.ExtUser{ + expectedUser: &openrtb2.User{ Consent: consent, }, }, { description: "Overrides Existing Consent", consent: consent, - userExt: &openrtb_ext.ExtUser{ + user: &openrtb2.User{ Consent: existingConsent, }, - expectedUserExt: openrtb_ext.ExtUser{ + expectedUser: &openrtb2.User{ Consent: consent, }, }, { description: "Overrides Existing Consent - With Sibling Data", consent: consent, - userExt: &openrtb_ext.ExtUser{ + user: &openrtb2.User{ Consent: existingConsent, }, - expectedUserExt: openrtb_ext.ExtUser{ + expectedUser: &openrtb2.User{ Consent: consent, }, }, { description: "Does Not Override Existing Consent If Empty", consent: "", - userExt: &openrtb_ext.ExtUser{ + user: &openrtb2.User{ Consent: existingConsent, }, - expectedUserExt: openrtb_ext.ExtUser{ + expectedUser: &openrtb2.User{ Consent: existingConsent, }, }, @@ -289,7 +295,7 @@ func TestGDPRConsent(t *testing.T) { for _, test := range testCases { // Build Request - bid, err := getTestBidRequest(test.nilUser, test.userExt, true, nil) + bid, err := getTestBidRequest(test.nilUser, test.user, true, nil) if err != nil { t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err) } @@ -299,10 +305,11 @@ func TestGDPRConsent(t *testing.T) { // Build Exchange Endpoint mockExchange := &mockAmpExchange{} + endpoint, _ := NewAmpEndpoint( fakeUUIDGenerator{}, mockExchange, - newParamsValidator(t), + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)), &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{ @@ -338,15 +345,8 @@ func TestGDPRConsent(t *testing.T) { if !assert.NotNil(t, result.User, test.description+":lastRequest.User") { return } - if !assert.NotNil(t, result.User.Ext, test.description+":lastRequest.User.Ext") { - return - } - var ue openrtb_ext.ExtUser - err = jsonutil.UnmarshalValid(result.User.Ext, &ue) - if !assert.NoError(t, err, test.description+":deserialize") { - return - } - assert.Equal(t, test.expectedUserExt, ue, test.description) + + assert.Equal(t, test.expectedUser, result.User, test.description) assert.Equal(t, expectedErrorsFromHoldAuction, response.ORTB2.Ext.Errors, test.description+":errors") assert.Empty(t, response.ORTB2.Ext.Warnings, test.description+":warnings") @@ -369,15 +369,8 @@ func TestGDPRConsent(t *testing.T) { if !assert.NotNil(t, resultLegacy.User, test.description+":legacy:lastRequest.User") { return } - if !assert.NotNil(t, resultLegacy.User.Ext, test.description+":legacy:lastRequest.User.Ext") { - return - } - var ueLegacy openrtb_ext.ExtUser - err = jsonutil.UnmarshalValid(resultLegacy.User.Ext, &ueLegacy) - if !assert.NoError(t, err, test.description+":legacy:deserialize") { - return - } - assert.Equal(t, test.expectedUserExt, ueLegacy, test.description+":legacy") + + assert.Equal(t, test.expectedUser, resultLegacy.User, test.description+":legacy") assert.Equal(t, expectedErrorsFromHoldAuction, responseLegacy.ORTB2.Ext.Errors, test.description+":legacy:errors") assert.Empty(t, responseLegacy.ORTB2.Ext.Warnings, test.description+":legacy:warnings") } @@ -550,28 +543,6 @@ func TestOverrideWithParams(t *testing.T) { errorMsgs: []string{"unable to merge imp.ext with targeting data, check targeting data is correct: Invalid JSON Patch"}, }, }, - { - desc: "bid request with malformed user.ext.prebid - amp.Params with GDPR consent values - expect policy writer to return error", - given: testInput{ - ampParams: amp.Params{ - ConsentType: amp.ConsentTCF2, - Consent: "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA", - }, - bidRequest: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}}, - User: &openrtb2.User{Ext: json.RawMessage(`{"prebid":{malformed}}`)}, - }, - }, - expected: testOutput{ - bidRequest: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}}, - User: &openrtb2.User{Ext: json.RawMessage(`{"prebid":{malformed}}`)}, - Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)}, - }, - errorMsgs: []string{"expect \" after {, but found m"}, - expectFatalErrors: true, - }, - }, } for _, test := range testCases { @@ -658,46 +629,46 @@ func TestCCPAConsent(t *testing.T) { var gdpr int8 = 1 testCases := []struct { - description string - consent string - regsExt *openrtb_ext.ExtRegs - nilRegs bool - expectedRegExt openrtb_ext.ExtRegs + description string + consent string + regs openrtb2.Regs + nilRegs bool + expectedReg *openrtb2.Regs }{ { description: "Nil Regs", consent: consent, nilRegs: true, - expectedRegExt: openrtb_ext.ExtRegs{ + expectedReg: &openrtb2.Regs{ USPrivacy: consent, }, }, { description: "Nil Regs Ext", consent: consent, - regsExt: nil, - expectedRegExt: openrtb_ext.ExtRegs{ + nilRegs: true, + expectedReg: &openrtb2.Regs{ USPrivacy: consent, }, }, { description: "Overrides Existing Consent", consent: consent, - regsExt: &openrtb_ext.ExtRegs{ + regs: openrtb2.Regs{ USPrivacy: existingConsent, }, - expectedRegExt: openrtb_ext.ExtRegs{ + expectedReg: &openrtb2.Regs{ USPrivacy: consent, }, }, { description: "Overrides Existing Consent - With Sibling Data", consent: consent, - regsExt: &openrtb_ext.ExtRegs{ + regs: openrtb2.Regs{ USPrivacy: existingConsent, GDPR: &gdpr, }, - expectedRegExt: openrtb_ext.ExtRegs{ + expectedReg: &openrtb2.Regs{ USPrivacy: consent, GDPR: &gdpr, }, @@ -705,10 +676,10 @@ func TestCCPAConsent(t *testing.T) { { description: "Does Not Override Existing Consent If Empty", consent: "", - regsExt: &openrtb_ext.ExtRegs{ + regs: openrtb2.Regs{ USPrivacy: existingConsent, }, - expectedRegExt: openrtb_ext.ExtRegs{ + expectedReg: &openrtb2.Regs{ USPrivacy: existingConsent, }, }, @@ -716,7 +687,7 @@ func TestCCPAConsent(t *testing.T) { for _, test := range testCases { // Build Request - bid, err := getTestBidRequest(true, nil, test.nilRegs, test.regsExt) + bid, err := getTestBidRequest(true, nil, test.nilRegs, &test.regs) if err != nil { t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err) } @@ -729,7 +700,7 @@ func TestCCPAConsent(t *testing.T) { endpoint, _ := NewAmpEndpoint( fakeUUIDGenerator{}, mockExchange, - newParamsValidator(t), + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)), &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, @@ -762,15 +733,8 @@ func TestCCPAConsent(t *testing.T) { if !assert.NotNil(t, result.Regs, test.description+":lastRequest.Regs") { return } - if !assert.NotNil(t, result.Regs.Ext, test.description+":lastRequest.Regs.Ext") { - return - } - var re openrtb_ext.ExtRegs - err = jsonutil.UnmarshalValid(result.Regs.Ext, &re) - if !assert.NoError(t, err, test.description+":deserialize") { - return - } - assert.Equal(t, test.expectedRegExt, re, test.description) + + assert.Equal(t, test.expectedReg, result.Regs, test.description) assert.Equal(t, expectedErrorsFromHoldAuction, response.ORTB2.Ext.Errors) assert.Empty(t, response.ORTB2.Ext.Warnings) } @@ -778,7 +742,7 @@ func TestCCPAConsent(t *testing.T) { func TestConsentWarnings(t *testing.T) { type inputTest struct { - regs *openrtb_ext.ExtRegs + regs *openrtb2.Regs invalidConsentURL bool expectedWarnings map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage } @@ -809,7 +773,7 @@ func TestConsentWarnings(t *testing.T) { expectedWarnings: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{openrtb_ext.BidderReservedGeneral: {invalidCCPAWarning}}, }, { - regs: &openrtb_ext.ExtRegs{USPrivacy: "invalid"}, + regs: &openrtb2.Regs{USPrivacy: "invalid"}, invalidConsentURL: true, expectedWarnings: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{ openrtb_ext.BidderReservedGeneral: {invalidCCPAWarning, invalidConsentWarning}, @@ -817,7 +781,7 @@ func TestConsentWarnings(t *testing.T) { }, }, { - regs: &openrtb_ext.ExtRegs{USPrivacy: "1NYN"}, + regs: &openrtb2.Regs{USPrivacy: "1NYN"}, invalidConsentURL: false, expectedWarnings: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{openrtb_ext.BidderName("appnexus"): {bidderWarning}}, }, @@ -843,7 +807,7 @@ func TestConsentWarnings(t *testing.T) { endpoint, _ := NewAmpEndpoint( fakeUUIDGenerator{}, mockExchange, - newParamsValidator(t), + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)), &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, @@ -900,17 +864,18 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { validConsentGDPR2 := "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA" testCases := []struct { - description string - consent string - consentLegacy string - userExt *openrtb_ext.ExtUser - expectedUserExt openrtb_ext.ExtUser + description string + consent string + consentLegacy string + user *openrtb2.User + expectedUser *openrtb2.User }{ { description: "New Consent Wins", consent: validConsentGDPR1, consentLegacy: validConsentGDPR2, - expectedUserExt: openrtb_ext.ExtUser{ + user: &openrtb2.User{}, + expectedUser: &openrtb2.User{ Consent: validConsentGDPR1, }, }, @@ -918,7 +883,8 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { description: "New Consent Wins - Reverse", consent: validConsentGDPR2, consentLegacy: validConsentGDPR1, - expectedUserExt: openrtb_ext.ExtUser{ + user: &openrtb2.User{}, + expectedUser: &openrtb2.User{ Consent: validConsentGDPR2, }, }, @@ -926,7 +892,7 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { for _, test := range testCases { // Build Request - bid, err := getTestBidRequest(false, nil, true, nil) + bid, err := getTestBidRequest(false, test.user, true, nil) if err != nil { t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err) } @@ -939,7 +905,7 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { endpoint, _ := NewAmpEndpoint( fakeUUIDGenerator{}, mockExchange, - newParamsValidator(t), + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)), &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{ @@ -975,15 +941,8 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { if !assert.NotNil(t, result.User, test.description+":lastRequest.User") { return } - if !assert.NotNil(t, result.User.Ext, test.description+":lastRequest.User.Ext") { - return - } - var ue openrtb_ext.ExtUser - err = jsonutil.UnmarshalValid(result.User.Ext, &ue) - if !assert.NoError(t, err, test.description+":deserialize") { - return - } - assert.Equal(t, test.expectedUserExt, ue, test.description) + + assert.Equal(t, test.expectedUser, result.User, test.description) assert.Equal(t, expectedErrorsFromHoldAuction, response.ORTB2.Ext.Errors) assert.Empty(t, response.ORTB2.Ext.Warnings) } @@ -997,7 +956,7 @@ func TestAMPSiteExt(t *testing.T) { endpoint, _ := NewAmpEndpoint( fakeUUIDGenerator{}, exchange, - newParamsValidator(t), + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)), &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, @@ -1027,21 +986,43 @@ func TestAMPSiteExt(t *testing.T) { } // TestBadRequests makes sure we return 400's on bad requests. +// RTB26: Will need to be fixed once all validation functions are updated to rtb 2.6 func TestAmpBadRequests(t *testing.T) { - dir := "sample-requests/invalid-whole" + dir := "sample-requests/invalid-whole/" files, err := os.ReadDir(dir) assert.NoError(t, err, "Failed to read folder: %s", dir) - badRequests := make(map[string]json.RawMessage, len(files)) + mockAmpStoredReq := make(map[string]json.RawMessage, len(files)) + badRequests := make(map[string]testCase, len(files)) + filemap := make(map[string]string, len(files)) for index, file := range files { - badRequests[strconv.Itoa(100+index)] = readFile(t, "sample-requests/invalid-whole/"+file.Name()) + filename := file.Name() + fileData := readFile(t, dir+filename) + + test, err := parseTestData(fileData, filename) + if !assert.NoError(t, err) { + return + } + + if skipAmpTest(test) { + continue + } + + requestID := strconv.Itoa(100 + index) + test.Query = fmt.Sprintf("account=test_pub&tag_id=%s", requestID) + + badRequests[requestID] = test + mockAmpStoredReq[requestID] = test.BidRequest + filemap[requestID] = filename } + addAmpBadRequests(badRequests, mockAmpStoredReq) + endpoint, _ := NewAmpEndpoint( fakeUUIDGenerator{}, &mockAmpExchange{}, - newParamsValidator(t), - &mockAmpStoredReqFetcher{badRequests}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)), + &mockAmpStoredReqFetcher{data: mockAmpStoredReq}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, &metricsConfig.NilMetricsEngine{}, @@ -1053,16 +1034,73 @@ func TestAmpBadRequests(t *testing.T) { hooks.EmptyPlanBuilder{}, nil, ) - for requestID := range badRequests { - request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=%s", requestID), nil) - recorder := httptest.NewRecorder() - endpoint(recorder, request, nil) + for id, test := range badRequests { + t.Run(filemap[id], func(t *testing.T) { + request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?%s", test.Query), nil) + recorder := httptest.NewRecorder() + + endpoint(recorder, request, nil) + + response := recorder.Body.String() + assert.Equal(t, test.ExpectedReturnCode, recorder.Code, test.Description) + assert.Contains(t, response, test.ExpectedErrorMessage, "Actual: %s \nExpected: %s. Description: %s \n", response, test.ExpectedErrorMessage, test.Description) + }) + } +} - if recorder.Code != http.StatusBadRequest { - t.Errorf("Expected status %d. Got %d. Input was: %s", http.StatusBadRequest, recorder.Code, fmt.Sprintf("/openrtb2/auction/amp?config=%s", requestID)) +func skipAmpTest(test testCase) bool { + bidRequest := openrtb2.BidRequest{} + if err := json.Unmarshal(test.BidRequest, &bidRequest); err == nil { + // request.app must not exist in AMP + if bidRequest.App != nil { + return true } + + // data for tag_id='%s' does not define the required imp array + // Invalid request: data for tag_id '%s' includes %d imp elements. Only one is allowed + if len(bidRequest.Imp) == 0 || len(bidRequest.Imp) > 1 { + return true + } + + if bidRequest.Device != nil && strings.Contains(string(bidRequest.Device.Ext), "interstitial") { + return true + } + } + + // request.ext.prebid.cache is initialised in AMP if it is not present in request + if strings.Contains(test.ExpectedErrorMessage, `Invalid request: request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`) || + strings.Contains(test.ExpectedErrorMessage, `Invalid request: ext.prebid.storedrequest.id must be a string`) { + return true + } + + return false +} + +func addAmpBadRequests(mapBadRequests map[string]testCase, mockAmpStoredReq map[string]json.RawMessage) { + mapBadRequests["201"] = testCase{ + Description: "missing-tag-id", + Query: "account=test_pub", + ExpectedReturnCode: http.StatusBadRequest, + ExpectedErrorMessage: "Invalid request: AMP requests require an AMP tag_id\n", + } + mockAmpStoredReq["201"] = json.RawMessage(`{}`) + + mapBadRequests["202"] = testCase{ + Description: "request.app-present", + Query: "account=test_pub&tag_id=202", + ExpectedReturnCode: http.StatusBadRequest, + ExpectedErrorMessage: "Invalid request: request.app must not exist in AMP stored requests.\n", + } + mockAmpStoredReq["202"] = json.RawMessage(`{"imp":[{}],"app":{}}`) + + mapBadRequests["203"] = testCase{ + Description: "request-with-2-imps", + Query: "account=test_pub&tag_id=203", + ExpectedReturnCode: http.StatusBadRequest, + ExpectedErrorMessage: "Invalid request: data for tag_id '203' includes 2 imp elements. Only one is allowed", } + mockAmpStoredReq["203"] = json.RawMessage(`{"imp":[{},{}]}`) } // TestAmpDebug makes sure we get debug information back when requested @@ -1074,7 +1112,7 @@ func TestAmpDebug(t *testing.T) { endpoint, _ := NewAmpEndpoint( fakeUUIDGenerator{}, &mockAmpExchange{}, - newParamsValidator(t), + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)), &mockAmpStoredReqFetcher{requests}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, @@ -1210,7 +1248,7 @@ func TestQueryParamOverrides(t *testing.T) { endpoint, _ := NewAmpEndpoint( fakeUUIDGenerator{}, &mockAmpExchange{}, - newParamsValidator(t), + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)), &mockAmpStoredReqFetcher{requests}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, @@ -1368,7 +1406,7 @@ func (s formatOverrideSpec) execute(t *testing.T) { endpoint, _ := NewAmpEndpoint( fakeUUIDGenerator{}, &mockAmpExchange{}, - newParamsValidator(t), + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)), &mockAmpStoredReqFetcher{requests}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, @@ -1478,7 +1516,7 @@ func (m *mockAmpExchangeWarnings) HoldAuction(ctx context.Context, r *exchange.A return &exchange.AuctionResponse{BidResponse: response}, nil } -func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, regsExt *openrtb_ext.ExtRegs) ([]byte, error) { +func getTestBidRequest(nilUser bool, user *openrtb2.User, nilRegs bool, regs *openrtb2.Regs) ([]byte, error) { var width int64 = 300 var height int64 = 300 bidRequest := &openrtb2.BidRequest{ @@ -1509,37 +1547,12 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, }, } - var userExtData []byte - if userExt != nil { - var err error - userExtData, err = jsonutil.Marshal(userExt) - if err != nil { - return nil, err - } - } - if !nilUser { - bidRequest.User = &openrtb2.User{ - ID: "aUserId", - BuyerUID: "aBuyerID", - Ext: userExtData, - } - } - - var regsExtData []byte - if regsExt != nil { - var err error - regsExtData, err = jsonutil.Marshal(regsExt) - if err != nil { - return nil, err - } + bidRequest.User = user } if !nilRegs { - bidRequest.Regs = &openrtb2.Regs{ - COPPA: 1, - Ext: regsExtData, - } + bidRequest.Regs = regs } return jsonutil.Marshal(bidRequest) } @@ -1657,6 +1670,7 @@ func (logger mockLogger) LogNotificationEventObject(uuidObj *analytics.Notificat func (logger mockLogger) LogAmpObject(ao *analytics.AmpObject, _ privacy.ActivityControl) { *logger.ampObject = *ao } +func (logger mockLogger) Shutdown() {} func TestBuildAmpObject(t *testing.T) { testCases := []struct { @@ -1909,7 +1923,7 @@ func ampObjectTestSetup(t *testing.T, inTagId string, inStoredRequest json.RawMe endpoint, _ := NewAmpEndpoint( fakeUUIDGenerator{id: "foo", err: nil}, exchange, - newParamsValidator(t), + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)), mockAmpFetcher, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize, GenerateRequestID: generateRequestID}, @@ -1962,7 +1976,7 @@ func TestAmpAuctionResponseHeaders(t *testing.T) { endpoint, _ := NewAmpEndpoint( fakeUUIDGenerator{}, exchange, - newParamsValidator(t), + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)), &mockAmpStoredReqFetcher{storedRequests}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, @@ -1977,7 +1991,7 @@ func TestAmpAuctionResponseHeaders(t *testing.T) { ) for _, test := range testCases { - httpReq := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp"+test.requestURLArguments), nil) + httpReq := httptest.NewRequest("GET", "/openrtb2/auction/amp"+test.requestURLArguments, nil) recorder := httptest.NewRecorder() endpoint(recorder, httpReq, nil) @@ -1998,7 +2012,7 @@ func TestRequestWithTargeting(t *testing.T) { endpoint, _ := NewAmpEndpoint( fakeUUIDGenerator{}, exchange, - newParamsValidator(t), + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)), &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, @@ -2404,3 +2418,87 @@ func TestSetSeatNonBid(t *testing.T) { }) } } + +func TestAmpAuctionDebugWarningsOnly(t *testing.T) { + testCases := []struct { + description string + requestURLArguments string + addRequestHeaders func(r *http.Request) + expectedStatus int + expectedWarnings map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage + }{ + { + description: "debug_enabled_request_with_invalid_Sec-Browsing-Topics_header", + requestURLArguments: "?tag_id=1&debug=1", + addRequestHeaders: func(r *http.Request) { + r.Header.Add("Sec-Browsing-Topics", "foo") + }, + expectedStatus: 200, + expectedWarnings: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{ + "general": { + { + Code: 10012, + Message: "Invalid field in Sec-Browsing-Topics header: foo", + }, + }, + }, + }, + { + description: "debug_disabled_request_with_invalid_Sec-Browsing-Topics_header", + requestURLArguments: "?tag_id=1", + addRequestHeaders: func(r *http.Request) { + r.Header.Add("Sec-Browsing-Topics", "foo") + }, + expectedStatus: 200, + expectedWarnings: nil, + }, + } + + storedRequests := map[string]json.RawMessage{ + "1": json.RawMessage(validRequest(t, "site.json")), + } + exchange := &nobidExchange{} + endpoint, _ := NewAmpEndpoint( + fakeUUIDGenerator{}, + exchange, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)), + &mockAmpStoredReqFetcher{storedRequests}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{ + MaxRequestSize: maxSize, + AccountDefaults: config.Account{ + Privacy: config.AccountPrivacy{ + PrivacySandbox: config.PrivacySandbox{ + TopicsDomain: "abc", + }, + }, + }, + }, + &metricsConfig.NilMetricsEngine{}, + analyticsBuild.New(&config.Analytics{}), + map[string]string{}, + []byte{}, + openrtb_ext.BuildBidderMap(), + empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, + nil, + ) + + for _, test := range testCases { + httpReq := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp"+test.requestURLArguments), nil) + test.addRequestHeaders(httpReq) + recorder := httptest.NewRecorder() + + endpoint(recorder, httpReq, nil) + + assert.Equal(t, test.expectedStatus, recorder.Result().StatusCode) + + // Parse Response + var response AmpResponse + if err := jsonutil.UnmarshalValid(recorder.Body.Bytes(), &response); err != nil { + t.Fatalf("Error unmarshalling response: %s", err.Error()) + } + + assert.Equal(t, test.expectedWarnings, response.ORTB2.Ext.Warnings) + } +} diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 2cad7a2463a..e4faa7e9cb8 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -20,49 +20,51 @@ import ( "github.com/julienschmidt/httprouter" gpplib "github.com/prebid/go-gpp" "github.com/prebid/go-gpp/constants" - "github.com/prebid/openrtb/v19/adcom1" - "github.com/prebid/openrtb/v19/native1" - nativeRequests "github.com/prebid/openrtb/v19/native1/request" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/openrtb/v19/openrtb3" - "github.com/prebid/prebid-server/bidadjustment" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/ortb" - "github.com/prebid/prebid-server/privacy" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/openrtb/v20/openrtb3" + "github.com/prebid/prebid-server/v3/bidadjustment" + "github.com/prebid/prebid-server/v3/hooks" + "github.com/prebid/prebid-server/v3/ortb" + "github.com/prebid/prebid-server/v3/privacy" + "github.com/prebid/prebid-server/v3/privacysandbox" + "github.com/prebid/prebid-server/v3/schain" "golang.org/x/net/publicsuffix" jsonpatch "gopkg.in/evanphx/json-patch.v4" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/privacy/lmt" - "github.com/prebid/prebid-server/schain" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/stored_responses" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/httputil" - "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/uuidutil" - "github.com/prebid/prebid-server/version" + accountService "github.com/prebid/prebid-server/v3/account" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/exchange" + "github.com/prebid/prebid-server/v3/gdpr" + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/prebid_cache_client" + "github.com/prebid/prebid-server/v3/privacy/ccpa" + "github.com/prebid/prebid-server/v3/privacy/lmt" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v3/stored_responses" + "github.com/prebid/prebid-server/v3/usersync" + "github.com/prebid/prebid-server/v3/util/httputil" + "github.com/prebid/prebid-server/v3/util/iputil" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/uuidutil" + "github.com/prebid/prebid-server/v3/version" ) -const storedRequestTimeoutMillis = 50 const ampChannel = "amp" const appChannel = "app" +const secCookieDeprecation = "Sec-Cookie-Deprecation" +const secBrowsingTopics = "Sec-Browsing-Topics" +const observeBrowsingTopics = "Observe-Browsing-Topics" +const observeBrowsingTopicsValue = "?1" var ( dntKey string = http.CanonicalHeaderKey("DNT") + secGPCKey string = http.CanonicalHeaderKey("Sec-GPC") dntDisabled int8 = 0 dntEnabled int8 = 1 notAmp int8 = 0 @@ -84,7 +86,7 @@ var accountIdSearchPath = [...]struct { func NewEndpoint( uuidGenerator uuidutil.UUIDGenerator, ex exchange.Exchange, - validator openrtb_ext.BidderParamValidator, + requestValidator ortb.RequestValidator, requestsById stored_requests.Fetcher, accounts stored_requests.AccountFetcher, cfg *config.Configuration, @@ -97,7 +99,7 @@ func NewEndpoint( hookExecutionPlanBuilder hooks.ExecutionPlanBuilder, tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed, ) (httprouter.Handle, error) { - if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || metricsEngine == nil { + if ex == nil || requestValidator == nil || requestsById == nil || accounts == nil || cfg == nil || metricsEngine == nil { return nil, errors.New("NewEndpoint requires non-nil arguments.") } @@ -111,7 +113,7 @@ func NewEndpoint( return httprouter.Handle((&endpointDeps{ uuidGenerator, ex, - validator, + requestValidator, requestsById, empty_fetcher.EmptyFetcher{}, accounts, @@ -131,12 +133,10 @@ func NewEndpoint( openrtb_ext.NormalizeBidderName}).Auction), nil } -type normalizeBidderName func(name string) (openrtb_ext.BidderName, bool) - type endpointDeps struct { uuidGenerator uuidutil.UUIDGenerator ex exchange.Exchange - paramsValidator openrtb_ext.BidderParamValidator + requestValidator ortb.RequestValidator storedReqFetcher stored_requests.Fetcher videoFetcher stored_requests.Fetcher accounts stored_requests.AccountFetcher @@ -153,7 +153,7 @@ type endpointDeps struct { storedRespFetcher stored_requests.Fetcher hookExecutionPlanBuilder hooks.ExecutionPlanBuilder tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed - normalizeBidderName normalizeBidderName + normalizeBidderName openrtb_ext.BidderNameNormalizer } func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { @@ -189,6 +189,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http }() w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver)) + setBrowsingTopicsHeader(w, r) req, impExtInfoMap, storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, account, errL := deps.parseRequest(r, &labels, hookExecutor) if errortypes.ContainsFatalError(errL) && writeError(errL, w, &labels) { @@ -205,6 +206,9 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http activityControl = privacy.NewActivityControl(&account.Privacy) + hookExecutor.SetActivityControl(activityControl) + hookExecutor.SetAccount(account) + ctx := context.Background() timeout := deps.cfg.AuctionTimeouts.LimitAuctionTimeout(time.Duration(req.TMax) * time.Millisecond) @@ -389,6 +393,13 @@ func sendAuctionResponse( return labels, ao } +// setBrowsingTopicsHeader always set the Observe-Browsing-Topics header to a value of ?1 if the Sec-Browsing-Topics is present in request +func setBrowsingTopicsHeader(w http.ResponseWriter, r *http.Request) { + if value := r.Header.Get(secBrowsingTopics); value != "" { + w.Header().Set(observeBrowsingTopics, observeBrowsingTopicsValue) + } +} + // parseRequest turns the HTTP request into an OpenRTB request. This is guaranteed to return: // // - A context which times out appropriately, given the request. @@ -402,6 +413,7 @@ func sendAuctionResponse( func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metrics.Labels, hookExecutor hookexecution.HookStageExecutor) (req *openrtb_ext.RequestWrapper, impExtInfoMap map[string]exchange.ImpExtInfo, storedAuctionResponses stored_responses.ImpsWithBidResponses, storedBidResponses stored_responses.ImpBidderStoredResp, bidderImpReplaceImpId stored_responses.BidderImpReplaceImpID, account *config.Account, errs []error) { errs = nil var err error + var errL []error var r io.ReadCloser = httpRequest.Body reqContentEncoding := httputil.ContentEncoding(httpRequest.Header.Get("Content-Encoding")) if reqContentEncoding != "" { @@ -452,7 +464,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric return } - timeout := parseTimeout(requestJson, time.Duration(storedRequestTimeoutMillis)*time.Millisecond) + timeout := parseTimeout(requestJson, time.Duration(deps.cfg.StoredRequestsTimeout)*time.Millisecond) ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() @@ -517,13 +529,13 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric return } - //Stored auction responses should be processed after stored requests due to possible impression modification - storedAuctionResponses, storedBidResponses, bidderImpReplaceImpId, errs = stored_responses.ProcessStoredResponses(ctx, requestJson, deps.storedRespFetcher, deps.bidderMap) - if len(errs) > 0 { - return nil, nil, nil, nil, nil, nil, errs + if err := jsonutil.UnmarshalValid(requestJson, req.BidRequest); err != nil { + errs = []error{err} + return } - if err := jsonutil.UnmarshalValid(requestJson, req.BidRequest); err != nil { + // normalize to openrtb 2.6 + if err := openrtb_ext.ConvertUpTo26(req); err != nil { errs = []error{err} return } @@ -534,7 +546,9 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric } // Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers). - deps.setFieldsImplicitly(httpRequest, req) + if errsL := deps.setFieldsImplicitly(httpRequest, req, account); len(errsL) > 0 { + errs = append(errs, errsL...) + } if err := ortb.SetDefaults(req); err != nil { errs = []error{err} @@ -548,8 +562,15 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric lmt.ModifyForIOS(req.BidRequest) - hasStoredResponses := len(storedAuctionResponses) > 0 - errL := deps.validateRequest(req, false, hasStoredResponses, storedBidResponses, hasStoredBidRequest) + //Stored auction responses should be processed after stored requests due to possible impression modification + storedAuctionResponses, storedBidResponses, bidderImpReplaceImpId, errL = stored_responses.ProcessStoredResponses(ctx, req, deps.storedRespFetcher) + if len(errL) > 0 { + errs = append(errs, errL...) + return nil, nil, nil, nil, nil, nil, errs + } + + hasStoredAuctionResponses := len(storedAuctionResponses) > 0 + errL = deps.validateRequest(account, httpRequest, req, false, hasStoredAuctionResponses, storedBidResponses, hasStoredBidRequest) if len(errL) > 0 { errs = append(errs, errL...) } @@ -654,7 +675,7 @@ func mergeBidderParamsImpExt(impExt *openrtb_ext.ImpExt, reqExtParams map[string extMapModified := false for bidder, params := range reqExtParams { - if !isPossibleBidder(bidder) { + if !openrtb_ext.IsPotentialBidder(bidder) { continue } @@ -743,7 +764,7 @@ func mergeBidderParamsImpExtPrebid(impExt *openrtb_ext.ImpExt, reqExtParams map[ return nil } -func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp bool, hasStoredResponses bool, storedBidResp stored_responses.ImpBidderStoredResp, hasStoredBidRequest bool) []error { +func (deps *endpointDeps) validateRequest(account *config.Account, httpReq *http.Request, req *openrtb_ext.RequestWrapper, isAmp bool, hasStoredAuctionResponses bool, storedBidResp stored_responses.ImpBidderStoredResp, hasStoredBidRequest bool) []error { errL := []error{} if req.ID == "" { return []error{errors.New("request missing required field: \"id\"")} @@ -809,10 +830,6 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp } } - if err := mapSChains(req); err != nil { - return []error{err} - } - if err := validateOrFillChannel(req, isAmp); err != nil { return []error{err} } @@ -841,10 +858,11 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp } var gpp gpplib.GppContainer if req.BidRequest.Regs != nil && len(req.BidRequest.Regs.GPP) > 0 { - gpp, err = gpplib.Parse(req.BidRequest.Regs.GPP) - if err != nil { + var errs []error + gpp, errs = gpplib.Parse(req.BidRequest.Regs.GPP) + if len(errs) > 0 { errL = append(errL, &errortypes.Warning{ - Message: fmt.Sprintf("GPP consent string is invalid and will be ignored. (%v)", err), + Message: fmt.Sprintf("GPP consent string is invalid and will be ignored. (%v)", errs[0]), WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) } } @@ -871,6 +889,10 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp return append(errL, err) } + if err := validateOrFillCookieDeprecation(httpReq, req, account); err != nil { + errL = append(errL, err) + } + if ccpaPolicy, err := ccpa.ReadFromRequestWrapper(req, gpp); err != nil { errL = append(errL, err) if errortypes.ContainsFatalError([]error{err}) { @@ -899,7 +921,7 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp } impIDs[imp.ID] = i - errs := deps.validateImp(imp, requestAliases, i, hasStoredResponses, storedBidResp) + errs := deps.requestValidator.ValidateImp(imp, ortb.ValidationConfig{}, i, requestAliases, hasStoredAuctionResponses, storedBidResp) if len(errs) > 0 { errL = append(errL, errs...) } @@ -911,32 +933,6 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp return errL } -// mapSChains maps an schain defined in an ORTB 2.4 location (req.ext.schain) to the ORTB 2.5 location -// (req.source.ext.schain) if no ORTB 2.5 schain (req.source.ext.schain, req.ext.prebid.schains) exists. -// An ORTB 2.4 schain is always deleted from the 2.4 location regardless of whether an ORTB 2.5 schain exists. -func mapSChains(req *openrtb_ext.RequestWrapper) error { - reqExt, err := req.GetRequestExt() - if err != nil { - return fmt.Errorf("req.ext is invalid: %v", err) - } - sourceExt, err := req.GetSourceExt() - if err != nil { - return fmt.Errorf("source.ext is invalid: %v", err) - } - - reqExtSChain := reqExt.GetSChain() - reqExt.SetSChain(nil) - - if reqPrebid := reqExt.GetPrebid(); reqPrebid != nil && reqPrebid.SChains != nil { - return nil - } else if sourceExt.GetSChain() != nil { - return nil - } else if reqExtSChain != nil { - sourceExt.SetSChain(reqExtSChain) - } - return nil -} - func validateAndFillSourceTID(req *openrtb_ext.RequestWrapper, generateRequestID bool, hasStoredBidRequest bool, isAmp bool) error { if req.Source == nil { req.Source = &openrtb2.Source{} @@ -1044,560 +1040,6 @@ func (deps *endpointDeps) validateBidders(bidders []string, knownBidders map[str return nil } -func (deps *endpointDeps) validateImp(imp *openrtb_ext.ImpWrapper, aliases map[string]string, index int, hasStoredResponses bool, storedBidResp stored_responses.ImpBidderStoredResp) []error { - if imp.ID == "" { - return []error{fmt.Errorf("request.imp[%d] missing required field: \"id\"", index)} - } - - if len(imp.Metric) != 0 { - return []error{fmt.Errorf("request.imp[%d].metric is not yet supported by prebid-server. Support may be added in the future", index)} - } - - if imp.Banner == nil && imp.Video == nil && imp.Audio == nil && imp.Native == nil { - return []error{fmt.Errorf("request.imp[%d] must contain at least one of \"banner\", \"video\", \"audio\", or \"native\"", index)} - } - - if err := validateBanner(imp.Banner, index, isInterstitial(imp)); err != nil { - return []error{err} - } - - if err := validateVideo(imp.Video, index); err != nil { - return []error{err} - } - - if err := validateAudio(imp.Audio, index); err != nil { - return []error{err} - } - - if err := fillAndValidateNative(imp.Native, index); err != nil { - return []error{err} - } - - if err := validatePmp(imp.PMP, index); err != nil { - return []error{err} - } - - errL := deps.validateImpExt(imp, aliases, index, hasStoredResponses, storedBidResp) - if len(errL) != 0 { - return errL - } - - return nil -} - -func isInterstitial(imp *openrtb_ext.ImpWrapper) bool { - return imp.Instl == 1 -} - -func validateBanner(banner *openrtb2.Banner, impIndex int, isInterstitial bool) error { - if banner == nil { - return nil - } - - // The following fields were previously uints in the OpenRTB library we use, but have - // since been changed to ints. We decided to maintain the non-negative check. - if banner.W != nil && *banner.W < 0 { - return fmt.Errorf("request.imp[%d].banner.w must be a positive number", impIndex) - } - if banner.H != nil && *banner.H < 0 { - return fmt.Errorf("request.imp[%d].banner.h must be a positive number", impIndex) - } - - // The following fields are deprecated in the OpenRTB 2.5 spec but are still present - // in the OpenRTB library we use. Enforce they are not specified. - if banner.WMin != 0 { - return fmt.Errorf("request.imp[%d].banner uses unsupported property: \"wmin\". Use the \"format\" array instead.", impIndex) - } - if banner.WMax != 0 { - return fmt.Errorf("request.imp[%d].banner uses unsupported property: \"wmax\". Use the \"format\" array instead.", impIndex) - } - if banner.HMin != 0 { - return fmt.Errorf("request.imp[%d].banner uses unsupported property: \"hmin\". Use the \"format\" array instead.", impIndex) - } - if banner.HMax != 0 { - return fmt.Errorf("request.imp[%d].banner uses unsupported property: \"hmax\". Use the \"format\" array instead.", impIndex) - } - - hasRootSize := banner.H != nil && banner.W != nil && *banner.H > 0 && *banner.W > 0 - if !hasRootSize && len(banner.Format) == 0 && !isInterstitial { - return fmt.Errorf("request.imp[%d].banner has no sizes. Define \"w\" and \"h\", or include \"format\" elements.", impIndex) - } - - for i, format := range banner.Format { - if err := validateFormat(&format, impIndex, i); err != nil { - return err - } - } - - return nil -} - -func validateVideo(video *openrtb2.Video, impIndex int) error { - if video == nil { - return nil - } - - if len(video.MIMEs) < 1 { - return fmt.Errorf("request.imp[%d].video.mimes must contain at least one supported MIME type", impIndex) - } - - // The following fields were previously uints in the OpenRTB library we use, but have - // since been changed to ints. We decided to maintain the non-negative check. - if video.W < 0 { - return fmt.Errorf("request.imp[%d].video.w must be a positive number", impIndex) - } - if video.H < 0 { - return fmt.Errorf("request.imp[%d].video.h must be a positive number", impIndex) - } - if video.MinBitRate < 0 { - return fmt.Errorf("request.imp[%d].video.minbitrate must be a positive number", impIndex) - } - if video.MaxBitRate < 0 { - return fmt.Errorf("request.imp[%d].video.maxbitrate must be a positive number", impIndex) - } - - return nil -} - -func validateAudio(audio *openrtb2.Audio, impIndex int) error { - if audio == nil { - return nil - } - - if len(audio.MIMEs) < 1 { - return fmt.Errorf("request.imp[%d].audio.mimes must contain at least one supported MIME type", impIndex) - } - - // The following fields were previously uints in the OpenRTB library we use, but have - // since been changed to ints. We decided to maintain the non-negative check. - if audio.Sequence < 0 { - return fmt.Errorf("request.imp[%d].audio.sequence must be a positive number", impIndex) - } - if audio.MaxSeq < 0 { - return fmt.Errorf("request.imp[%d].audio.maxseq must be a positive number", impIndex) - } - if audio.MinBitrate < 0 { - return fmt.Errorf("request.imp[%d].audio.minbitrate must be a positive number", impIndex) - } - if audio.MaxBitrate < 0 { - return fmt.Errorf("request.imp[%d].audio.maxbitrate must be a positive number", impIndex) - } - - return nil -} - -// fillAndValidateNative validates the request, and assigns the Asset IDs as recommended by the Native v1.2 spec. -func fillAndValidateNative(n *openrtb2.Native, impIndex int) error { - if n == nil { - return nil - } - - if len(n.Request) == 0 { - return fmt.Errorf("request.imp[%d].native missing required property \"request\"", impIndex) - } - var nativePayload nativeRequests.Request - if err := jsonutil.UnmarshalValid(json.RawMessage(n.Request), &nativePayload); err != nil { - return err - } - - if err := validateNativeContextTypes(nativePayload.Context, nativePayload.ContextSubType, impIndex); err != nil { - return err - } - if err := validateNativePlacementType(nativePayload.PlcmtType, impIndex); err != nil { - return err - } - if err := fillAndValidateNativeAssets(nativePayload.Assets, impIndex); err != nil { - return err - } - if err := validateNativeEventTrackers(nativePayload.EventTrackers, impIndex); err != nil { - return err - } - - serialized, err := jsonutil.Marshal(nativePayload) - if err != nil { - return err - } - n.Request = string(serialized) - return nil -} - -func validateNativeContextTypes(cType native1.ContextType, cSubtype native1.ContextSubType, impIndex int) error { - if cType == 0 { - // Context is only recommended, so none is a valid type. - return nil - } - if cType < native1.ContextTypeContent || (cType > native1.ContextTypeProduct && cType < openrtb_ext.NativeExchangeSpecificLowerBound) { - return fmt.Errorf("request.imp[%d].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex) - } - if cSubtype < 0 { - return fmt.Errorf("request.imp[%d].native.request.contextsubtype value can't be less than 0. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex) - } - if cSubtype == 0 { - return nil - } - if cSubtype >= native1.ContextSubTypeGeneral && cSubtype <= native1.ContextSubTypeUserGenerated { - if cType != native1.ContextTypeContent && cType < openrtb_ext.NativeExchangeSpecificLowerBound { - return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype) - } - return nil - } - if cSubtype >= native1.ContextSubTypeSocial && cSubtype <= native1.ContextSubTypeChat { - if cType != native1.ContextTypeSocial && cType < openrtb_ext.NativeExchangeSpecificLowerBound { - return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype) - } - return nil - } - if cSubtype >= native1.ContextSubTypeSelling && cSubtype <= native1.ContextSubTypeProductReview { - if cType != native1.ContextTypeProduct && cType < openrtb_ext.NativeExchangeSpecificLowerBound { - return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype) - } - return nil - } - if cSubtype >= openrtb_ext.NativeExchangeSpecificLowerBound { - return nil - } - - return fmt.Errorf("request.imp[%d].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex) -} - -func validateNativePlacementType(pt native1.PlacementType, impIndex int) error { - if pt == 0 { - // Placement Type is only recommended, not required. - return nil - } - if pt < native1.PlacementTypeFeed || (pt > native1.PlacementTypeRecommendationWidget && pt < openrtb_ext.NativeExchangeSpecificLowerBound) { - return fmt.Errorf("request.imp[%d].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", impIndex) - } - return nil -} - -func fillAndValidateNativeAssets(assets []nativeRequests.Asset, impIndex int) error { - if len(assets) < 1 { - return fmt.Errorf("request.imp[%d].native.request.assets must be an array containing at least one object", impIndex) - } - - assetIDs := make(map[int64]struct{}, len(assets)) - - // If none of the asset IDs are defined by the caller, then prebid server should assign its own unique IDs. But - // if the caller did assign its own asset IDs, then prebid server will respect those IDs - assignAssetIDs := true - for i := 0; i < len(assets); i++ { - assignAssetIDs = assignAssetIDs && (assets[i].ID == 0) - } - - for i := 0; i < len(assets); i++ { - if err := validateNativeAsset(assets[i], impIndex, i); err != nil { - return err - } - - if assignAssetIDs { - assets[i].ID = int64(i) - continue - } - - // Each asset should have a unique ID thats assigned by the caller - if _, ok := assetIDs[assets[i].ID]; ok { - return fmt.Errorf("request.imp[%d].native.request.assets[%d].id is already being used by another asset. Each asset ID must be unique.", impIndex, i) - } - - assetIDs[assets[i].ID] = struct{}{} - } - - return nil -} - -func validateNativeAsset(asset nativeRequests.Asset, impIndex int, assetIndex int) error { - assetErr := "request.imp[%d].native.request.assets[%d] must define exactly one of {title, img, video, data}" - foundType := false - - if asset.Title != nil { - foundType = true - if err := validateNativeAssetTitle(asset.Title, impIndex, assetIndex); err != nil { - return err - } - } - - if asset.Img != nil { - if foundType { - return fmt.Errorf(assetErr, impIndex, assetIndex) - } - foundType = true - if err := validateNativeAssetImage(asset.Img, impIndex, assetIndex); err != nil { - return err - } - } - - if asset.Video != nil { - if foundType { - return fmt.Errorf(assetErr, impIndex, assetIndex) - } - foundType = true - if err := validateNativeAssetVideo(asset.Video, impIndex, assetIndex); err != nil { - return err - } - } - - if asset.Data != nil { - if foundType { - return fmt.Errorf(assetErr, impIndex, assetIndex) - } - foundType = true - if err := validateNativeAssetData(asset.Data, impIndex, assetIndex); err != nil { - return err - } - } - - if !foundType { - return fmt.Errorf(assetErr, impIndex, assetIndex) - } - - return nil -} - -func validateNativeEventTrackers(trackers []nativeRequests.EventTracker, impIndex int) error { - for i := 0; i < len(trackers); i++ { - if err := validateNativeEventTracker(trackers[i], impIndex, i); err != nil { - return err - } - } - return nil -} - -func validateNativeAssetTitle(title *nativeRequests.Title, impIndex int, assetIndex int) error { - if title.Len < 1 { - return fmt.Errorf("request.imp[%d].native.request.assets[%d].title.len must be a positive number", impIndex, assetIndex) - } - return nil -} - -func validateNativeEventTracker(tracker nativeRequests.EventTracker, impIndex int, eventIndex int) error { - if tracker.Event < native1.EventTypeImpression || (tracker.Event > native1.EventTypeViewableVideo50 && tracker.Event < openrtb_ext.NativeExchangeSpecificLowerBound) { - return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex) - } - if len(tracker.Methods) < 1 { - return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].method is required. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex) - } - for methodIndex, method := range tracker.Methods { - if method < native1.EventTrackingMethodImage || (method > native1.EventTrackingMethodJS && method < openrtb_ext.NativeExchangeSpecificLowerBound) { - return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].methods[%d] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex, methodIndex) - } - } - - return nil -} - -func validateNativeAssetImage(img *nativeRequests.Image, impIndex int, assetIndex int) error { - if img.W < 0 { - return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.w must be a positive integer", impIndex, assetIndex) - } - if img.H < 0 { - return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.h must be a positive integer", impIndex, assetIndex) - } - if img.WMin < 0 { - return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.wmin must be a positive integer", impIndex, assetIndex) - } - if img.HMin < 0 { - return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.hmin must be a positive integer", impIndex, assetIndex) - } - return nil -} - -func validateNativeAssetVideo(video *nativeRequests.Video, impIndex int, assetIndex int) error { - if len(video.MIMEs) < 1 { - return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.mimes must be an array with at least one MIME type", impIndex, assetIndex) - } - if video.MinDuration < 1 { - return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.minduration must be a positive integer", impIndex, assetIndex) - } - if video.MaxDuration < 1 { - return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.maxduration must be a positive integer", impIndex, assetIndex) - } - if err := validateNativeVideoProtocols(video.Protocols, impIndex, assetIndex); err != nil { - return err - } - - return nil -} - -func validateNativeAssetData(data *nativeRequests.Data, impIndex int, assetIndex int) error { - if data.Type < native1.DataAssetTypeSponsored || (data.Type > native1.DataAssetTypeCTAText && data.Type < 500) { - return fmt.Errorf("request.imp[%d].native.request.assets[%d].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", impIndex, assetIndex) - } - - return nil -} - -func validateNativeVideoProtocols(protocols []adcom1.MediaCreativeSubtype, impIndex int, assetIndex int) error { - if len(protocols) < 1 { - return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.protocols must be an array with at least one element", impIndex, assetIndex) - } - for i := 0; i < len(protocols); i++ { - if err := validateNativeVideoProtocol(protocols[i], impIndex, assetIndex, i); err != nil { - return err - } - } - return nil -} - -func validateNativeVideoProtocol(protocol adcom1.MediaCreativeSubtype, impIndex int, assetIndex int, protocolIndex int) error { - if protocol < adcom1.CreativeVAST10 || protocol > adcom1.CreativeDAAST10Wrapper { - return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.protocols[%d] is invalid. See Section 5.8: https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf#page=52", impIndex, assetIndex, protocolIndex) - } - return nil -} - -func validateFormat(format *openrtb2.Format, impIndex, formatIndex int) error { - usesHW := format.W != 0 || format.H != 0 - usesRatios := format.WMin != 0 || format.WRatio != 0 || format.HRatio != 0 - - // The following fields were previously uints in the OpenRTB library we use, but have - // since been changed to ints. We decided to maintain the non-negative check. - if format.W < 0 { - return fmt.Errorf("request.imp[%d].banner.format[%d].w must be a positive number", impIndex, formatIndex) - } - if format.H < 0 { - return fmt.Errorf("request.imp[%d].banner.format[%d].h must be a positive number", impIndex, formatIndex) - } - if format.WRatio < 0 { - return fmt.Errorf("request.imp[%d].banner.format[%d].wratio must be a positive number", impIndex, formatIndex) - } - if format.HRatio < 0 { - return fmt.Errorf("request.imp[%d].banner.format[%d].hratio must be a positive number", impIndex, formatIndex) - } - if format.WMin < 0 { - return fmt.Errorf("request.imp[%d].banner.format[%d].wmin must be a positive number", impIndex, formatIndex) - } - - if usesHW && usesRatios { - return fmt.Errorf("Request imp[%d].banner.format[%d] should define *either* {w, h} *or* {wmin, wratio, hratio}, but not both. If both are valid, send two \"format\" objects in the request.", impIndex, formatIndex) - } - if !usesHW && !usesRatios { - return fmt.Errorf("Request imp[%d].banner.format[%d] should define *either* {w, h} (for static size requirements) *or* {wmin, wratio, hratio} (for flexible sizes) to be non-zero.", impIndex, formatIndex) - } - if usesHW && (format.W == 0 || format.H == 0) { - return fmt.Errorf("Request imp[%d].banner.format[%d] must define non-zero \"h\" and \"w\" properties.", impIndex, formatIndex) - } - if usesRatios && (format.WMin == 0 || format.WRatio == 0 || format.HRatio == 0) { - return fmt.Errorf("Request imp[%d].banner.format[%d] must define non-zero \"wmin\", \"wratio\", and \"hratio\" properties.", impIndex, formatIndex) - } - return nil -} - -func validatePmp(pmp *openrtb2.PMP, impIndex int) error { - if pmp == nil { - return nil - } - - for dealIndex, deal := range pmp.Deals { - if deal.ID == "" { - return fmt.Errorf("request.imp[%d].pmp.deals[%d] missing required field: \"id\"", impIndex, dealIndex) - } - } - return nil -} - -func (deps *endpointDeps) validateImpExt(imp *openrtb_ext.ImpWrapper, aliases map[string]string, impIndex int, hasStoredResponses bool, storedBidResp stored_responses.ImpBidderStoredResp) []error { - if len(imp.Ext) == 0 { - return []error{fmt.Errorf("request.imp[%d].ext is required", impIndex)} - } - - impExt, err := imp.GetImpExt() - if err != nil { - return []error{err} - } - - prebid := impExt.GetOrCreatePrebid() - prebidModified := false - - if prebid.Bidder == nil { - prebid.Bidder = make(map[string]json.RawMessage) - } - - ext := impExt.GetExt() - extModified := false - - // promote imp[].ext.BIDDER to newer imp[].ext.prebid.bidder.BIDDER location, with the later taking precedence - for k, v := range ext { - if isPossibleBidder(k) { - if _, exists := prebid.Bidder[k]; !exists { - prebid.Bidder[k] = v - prebidModified = true - } - delete(ext, k) - extModified = true - } - } - - if hasStoredResponses && prebid.StoredAuctionResponse == nil { - return []error{fmt.Errorf("request validation failed. The StoredAuctionResponse.ID field must be completely present with, or completely absent from, all impressions in request. No StoredAuctionResponse data found for request.imp[%d].ext.prebid \n", impIndex)} - } - - if len(storedBidResp) > 0 { - if err := validateStoredBidRespAndImpExtBidders(prebid.Bidder, storedBidResp, imp.ID); err != nil { - return []error{err} - } - } - - errL := []error{} - - for bidder, ext := range prebid.Bidder { - coreBidder, _ := openrtb_ext.NormalizeBidderName(bidder) - if tmp, isAlias := aliases[bidder]; isAlias { - coreBidder = openrtb_ext.BidderName(tmp) - } - - if coreBidderNormalized, isValid := deps.bidderMap[coreBidder.String()]; isValid { - if err := deps.paramsValidator.Validate(coreBidderNormalized, ext); err != nil { - return []error{fmt.Errorf("request.imp[%d].ext.prebid.bidder.%s failed validation.\n%v", impIndex, bidder, err)} - } - } else { - if msg, isDisabled := deps.disabledBidders[bidder]; isDisabled { - errL = append(errL, &errortypes.BidderTemporarilyDisabled{Message: msg}) - delete(prebid.Bidder, bidder) - prebidModified = true - } else { - return []error{fmt.Errorf("request.imp[%d].ext.prebid.bidder contains unknown bidder: %s. Did you forget an alias in request.ext.prebid.aliases?", impIndex, bidder)} - } - } - } - - if len(prebid.Bidder) == 0 { - errL = append(errL, fmt.Errorf("request.imp[%d].ext.prebid.bidder must contain at least one bidder", impIndex)) - return errL - } - - if prebidModified { - impExt.SetPrebid(prebid) - } - if extModified { - impExt.SetExt(ext) - } - - return errL -} - -// isPossibleBidder determines if a bidder name is a potential real bidder. -func isPossibleBidder(bidder string) bool { - switch openrtb_ext.BidderName(bidder) { - case openrtb_ext.BidderReservedContext: - return false - case openrtb_ext.BidderReservedData: - return false - case openrtb_ext.BidderReservedGPID: - return false - case openrtb_ext.BidderReservedPrebid: - return false - case openrtb_ext.BidderReservedSKAdN: - return false - case openrtb_ext.BidderReservedTID: - return false - case openrtb_ext.BidderReservedAE: - return false - default: - return true - } -} - func (deps *endpointDeps) parseBidExt(req *openrtb_ext.RequestWrapper) error { if _, err := req.GetRequestExt(); err != nil { return fmt.Errorf("request.ext is invalid: %v", err) @@ -1694,29 +1136,27 @@ func validateTargeting(t *openrtb_ext.ExtRequestTargeting) error { return nil } - if (t.IncludeWinners == nil || !*t.IncludeWinners) && (t.IncludeBidderKeys == nil || !*t.IncludeBidderKeys) { - return errors.New("ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support") - } - if t.PriceGranularity != nil { if err := validatePriceGranularity(t.PriceGranularity); err != nil { return err } } - if t.MediaTypePriceGranularity.Video != nil { - if err := validatePriceGranularity(t.MediaTypePriceGranularity.Video); err != nil { - return err + if t.MediaTypePriceGranularity != nil { + if t.MediaTypePriceGranularity.Video != nil { + if err := validatePriceGranularity(t.MediaTypePriceGranularity.Video); err != nil { + return err + } } - } - if t.MediaTypePriceGranularity.Banner != nil { - if err := validatePriceGranularity(t.MediaTypePriceGranularity.Banner); err != nil { - return err + if t.MediaTypePriceGranularity.Banner != nil { + if err := validatePriceGranularity(t.MediaTypePriceGranularity.Banner); err != nil { + return err + } } - } - if t.MediaTypePriceGranularity.Native != nil { - if err := validatePriceGranularity(t.MediaTypePriceGranularity.Native); err != nil { - return err + if t.MediaTypePriceGranularity.Native != nil { + if err := validatePriceGranularity(t.MediaTypePriceGranularity.Native); err != nil { + return err + } } } @@ -1772,8 +1212,8 @@ func (deps *endpointDeps) validateApp(req *openrtb_ext.RequestWrapper) error { } if req.App.ID != "" { - if _, found := deps.cfg.BlacklistedAppMap[req.App.ID]; found { - return &errortypes.BlacklistedApp{Message: fmt.Sprintf("Prebid-server does not process requests from App ID: %s", req.App.ID)} + if _, found := deps.cfg.BlockedAppsLookup[req.App.ID]; found { + return &errortypes.BlockedApp{Message: fmt.Sprintf("Prebid-server does not process requests from App ID: %s", req.App.ID)} } } @@ -1818,6 +1258,7 @@ func (deps *endpointDeps) validateUser(req *openrtb_ext.RequestWrapper, aliases if err != nil { return append(errL, fmt.Errorf("request.user.ext object is not valid: %v", err)) } + // Check if the buyeruids are valid prebid := userExt.GetPrebid() if prebid != nil { @@ -1834,28 +1275,20 @@ func (deps *endpointDeps) validateUser(req *openrtb_ext.RequestWrapper, aliases } } } + // Check Universal User ID - eids := userExt.GetEid() - if eids != nil { - eidsValue := *eids - uniqueSources := make(map[string]struct{}, len(eidsValue)) - for eidIndex, eid := range eidsValue { - if eid.Source == "" { - return append(errL, fmt.Errorf("request.user.ext.eids[%d] missing required field: \"source\"", eidIndex)) - } - if _, ok := uniqueSources[eid.Source]; ok { - return append(errL, errors.New("request.user.ext.eids must contain unique sources")) - } - uniqueSources[eid.Source] = struct{}{} + for eidIndex, eid := range req.User.EIDs { + if eid.Source == "" { + return append(errL, fmt.Errorf("request.user.eids[%d] missing required field: \"source\"", eidIndex)) + } - if len(eid.UIDs) == 0 { - return append(errL, fmt.Errorf("request.user.ext.eids[%d].uids must contain at least one element or be undefined", eidIndex)) - } + if len(eid.UIDs) == 0 { + return append(errL, fmt.Errorf("request.user.eids[%d].uids must contain at least one element or be undefined", eidIndex)) + } - for uidIndex, uid := range eid.UIDs { - if uid.ID == "" { - return append(errL, fmt.Errorf("request.user.ext.eids[%d].uids[%d] missing required field: \"id\"", eidIndex, uidIndex)) - } + for uidIndex, uid := range eid.UIDs { + if uid.ID == "" { + return append(errL, fmt.Errorf("request.user.eids[%d].uids[%d] missing required field: \"id\"", eidIndex, uidIndex)) } } } @@ -1884,16 +1317,11 @@ func validateRegs(req *openrtb_ext.RequestWrapper, gpp gpplib.GppContainer) []er WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) } } - regsExt, err := req.GetRegExt() - if err != nil { - return append(errL, fmt.Errorf("request.regs.ext is invalid: %v", err)) - } - gdpr := regsExt.GetGDPR() - if gdpr != nil && *gdpr != 0 && *gdpr != 1 { - return append(errL, errors.New("request.regs.ext.gdpr must be either 0 or 1")) + reqGDPR := req.BidRequest.Regs.GDPR + if reqGDPR != nil && *reqGDPR != 0 && *reqGDPR != 1 { + return append(errL, errors.New("request.regs.gdpr must be either 0 or 1")) } - return errL } @@ -1916,7 +1344,35 @@ func validateDevice(device *openrtb2.Device) error { if device.Geo != nil && device.Geo.Accuracy < 0 { return errors.New("request.device.geo.accuracy must be a positive number") } + return nil +} + +func validateOrFillCookieDeprecation(httpReq *http.Request, req *openrtb_ext.RequestWrapper, account *config.Account) error { + if account == nil || !account.Privacy.PrivacySandbox.CookieDeprecation.Enabled { + return nil + } + + deviceExt, err := req.GetDeviceExt() + if err != nil { + return err + } + + if deviceExt.GetCDep() != "" { + return nil + } + secCookieDeprecation := httpReq.Header.Get(secCookieDeprecation) + if secCookieDeprecation == "" { + return nil + } + if len(secCookieDeprecation) > 100 { + return &errortypes.Warning{ + Message: "request.device.ext.cdep must not exceed 100 characters", + WarningCode: errortypes.SecCookieDeprecationLenWarningCode, + } + } + + deviceExt.SetCDep(secCookieDeprecation) return nil } @@ -2001,7 +1457,7 @@ func sanitizeRequest(r *openrtb_ext.RequestWrapper, ipValidator iputil.IPValidat // OpenRTB properties from the headers and other implicit info. // // This function _should not_ override any fields which were defined explicitly by the caller in the request. -func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper) { +func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper, account *config.Account) []error { sanitizeRequest(r, deps.privateNetworkIPValidator) setDeviceImplicitly(httpReq, r, deps.privateNetworkIPValidator) @@ -2013,6 +1469,14 @@ func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, r *openrtb_ } setAuctionTypeImplicitly(r) + + err := setGPCImplicitly(httpReq, r) + if err != nil { + return []error{err} + } + + errs := setSecBrowsingTopicsImplicitly(httpReq, r, account) + return errs } // setDeviceImplicitly uses implicit info from httpReq to populate bidReq.Device @@ -2020,7 +1484,6 @@ func setDeviceImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper, i setIPImplicitly(httpReq, r, ipValidtor) setUAImplicitly(httpReq, r) setDoNotTrackImplicitly(httpReq, r) - } // setAuctionTypeImplicitly sets the auction type to 1 if it wasn't on the request, @@ -2031,6 +1494,53 @@ func setAuctionTypeImplicitly(r *openrtb_ext.RequestWrapper) { } } +func setGPCImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper) error { + secGPC := httpReq.Header.Get(secGPCKey) + + if secGPC != "1" { + return nil + } + + regExt, err := r.GetRegExt() + if err != nil { + return err + } + + if regExt.GetGPC() != nil { + return nil + } + + gpc := "1" + regExt.SetGPC(&gpc) + + return nil +} + +// setSecBrowsingTopicsImplicitly updates user.data with data from request header 'Sec-Browsing-Topics' +func setSecBrowsingTopicsImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper, account *config.Account) []error { + secBrowsingTopics := httpReq.Header.Get(secBrowsingTopics) + if secBrowsingTopics == "" { + return nil + } + + // host must configure privacy sandbox + if account == nil || account.Privacy.PrivacySandbox.TopicsDomain == "" { + return nil + } + + topics, errs := privacysandbox.ParseTopicsFromHeader(secBrowsingTopics) + if len(topics) == 0 { + return errs + } + + if r.User == nil { + r.User = &openrtb2.User{} + } + + r.User.Data = privacysandbox.UpdateUserDataWithTopics(r.User.Data, topics, account.Privacy.PrivacySandbox.TopicsDomain) + return errs +} + func setSiteImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper) { if r.Site == nil { r.Site = &openrtb2.Site{} @@ -2166,9 +1676,9 @@ func (deps *endpointDeps) processStoredRequests(requestJson []byte, impInfo []Im } } - // Apply default aliases, if they are provided + // apply default stored request if deps.defaultRequest { - aliasedRequest, err := jsonpatch.MergePatch(deps.defReqJSON, resolvedRequest) + merged, err := jsonpatch.MergePatch(deps.defReqJSON, resolvedRequest) if err != nil { hasErr, Err := getJsonSyntaxError(resolvedRequest) if hasErr { @@ -2181,7 +1691,7 @@ func (deps *endpointDeps) processStoredRequests(requestJson []byte, impInfo []Im } return nil, nil, []error{err} } - resolvedRequest = aliasedRequest + resolvedRequest = merged } // Apply any Stored Imps, if they exist. Since the JSON Merge Patch overrides arrays, @@ -2252,8 +1762,8 @@ func (deps *endpointDeps) processStoredRequests(requestJson []byte, impInfo []Im // parseImpInfo parses the request JSON and returns impression and unmarshalled imp.ext.prebid func parseImpInfo(requestJson []byte) (impData []ImpExtPrebidData, errs []error) { if impArray, dataType, _, err := jsonparser.Get(requestJson, "imp"); err == nil && dataType == jsonparser.Array { - _, err = jsonparser.ArrayEach(impArray, func(imp []byte, _ jsonparser.ValueType, _ int, err error) { - impExtData, _, _, err := jsonparser.Get(imp, "ext", "prebid") + _, _ = jsonparser.ArrayEach(impArray, func(imp []byte, _ jsonparser.ValueType, _ int, _ error) { + impExtData, _, _, _ := jsonparser.Get(imp, "ext", "prebid") var impExtPrebid openrtb_ext.ExtImpPrebid if impExtData != nil { if err := jsonutil.Unmarshal(impExtData, &impExtPrebid); err != nil { @@ -2360,9 +1870,9 @@ func writeError(errs []error, w http.ResponseWriter, labels *metrics.Labels) boo metricsStatus := metrics.RequestStatusBadInput for _, err := range errs { erVal := errortypes.ReadCode(err) - if erVal == errortypes.BlacklistedAppErrorCode || erVal == errortypes.AccountDisabledErrorCode { + if erVal == errortypes.BlockedAppErrorCode || erVal == errortypes.AccountDisabledErrorCode { httpStatus = http.StatusServiceUnavailable - metricsStatus = metrics.RequestStatusBlacklisted + metricsStatus = metrics.RequestStatusBlockedApp break } else if erVal == errortypes.MalformedAcctErrorCode { httpStatus = http.StatusInternalServerError @@ -2373,7 +1883,7 @@ func writeError(errs []error, w http.ResponseWriter, labels *metrics.Labels) boo w.WriteHeader(httpStatus) labels.RequestStatus = metricsStatus for _, err := range errs { - w.Write([]byte(fmt.Sprintf("Invalid request: %s\n", err.Error()))) + fmt.Fprintf(w, "Invalid request: %s\n", err.Error()) } rc = true } @@ -2501,27 +2011,3 @@ func checkIfAppRequest(request []byte) (bool, error) { } return false, nil } - -func validateStoredBidRespAndImpExtBidders(bidderExts map[string]json.RawMessage, storedBidResp stored_responses.ImpBidderStoredResp, impId string) error { - if bidResponses, ok := storedBidResp[impId]; ok { - if len(bidResponses) != len(bidderExts) { - return generateStoredBidResponseValidationError(impId) - } - - for bidderName := range bidResponses { - bidder := bidderName - normalizedCoreBidder, ok := openrtb_ext.NormalizeBidderName(bidder) - if ok { - bidder = normalizedCoreBidder.String() - } - if _, present := bidderExts[bidder]; !present { - return generateStoredBidResponseValidationError(impId) - } - } - } - return nil -} - -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) -} diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go index f3e82dca099..9697687407e 100644 --- a/endpoints/openrtb2/auction_benchmark_test.go +++ b/endpoints/openrtb2/auction_benchmark_test.go @@ -10,17 +10,18 @@ import ( "testing" "time" - analyticsBuild "github.com/prebid/prebid-server/analytics/build" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/macros" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/usersync" + analyticsBuild "github.com/prebid/prebid-server/v3/analytics/build" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/exchange" + "github.com/prebid/prebid-server/v3/experiment/adscert" + "github.com/prebid/prebid-server/v3/hooks" + "github.com/prebid/prebid-server/v3/macros" + metricsConfig "github.com/prebid/prebid-server/v3/metrics/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/ortb" + "github.com/prebid/prebid-server/v3/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v3/usersync" ) // benchmarkTestServer returns the header bidding test ad. This response was scraped from a real appnexus server response. @@ -71,6 +72,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { if err != nil { return } + requestValidator := ortb.NewRequestValidator(nil, map[string]string{}, paramValidator) nilMetrics := &metricsConfig.NilMetricsEngine{} @@ -87,6 +89,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { adapters, nil, &config.Configuration{}, + requestValidator, map[string]usersync.Syncer{}, nilMetrics, infos, @@ -95,12 +98,13 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { empty_fetcher.EmptyFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), + nil, ) endpoint, _ := NewEndpoint( fakeUUIDGenerator{}, exchange, - paramValidator, + requestValidator, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, @@ -148,8 +152,8 @@ func BenchmarkValidWholeExemplary(b *testing.B) { cfg := &config.Configuration{ MaxRequestSize: maxSize, - BlacklistedApps: test.Config.BlacklistedApps, - BlacklistedAppMap: test.Config.getBlacklistedAppMap(), + BlockedApps: test.Config.BlockedApps, + BlockedAppsLookup: test.Config.getBlockedAppLookup(), AccountRequired: test.Config.AccountRequired, } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 530d197e2ae..dfc4f441cfe 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -13,36 +13,42 @@ import ( "net/http/httptest" "os" "path/filepath" + "sort" "strings" "testing" "time" "github.com/buger/jsonparser" + jsoniter "github.com/json-iterator/go" "github.com/julienschmidt/httprouter" - "github.com/prebid/openrtb/v19/native1" - nativeRequests "github.com/prebid/openrtb/v19/native1/request" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/analytics" - analyticsBuild "github.com/prebid/prebid-server/analytics/build" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/metrics" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/stored_responses" - "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/analytics" + analyticsBuild "github.com/prebid/prebid-server/v3/analytics/build" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/exchange" + "github.com/prebid/prebid-server/v3/hooks" + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/hooks/hookstage" + "github.com/prebid/prebid-server/v3/metrics" + metricsConfig "github.com/prebid/prebid-server/v3/metrics/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/ortb" + "github.com/prebid/prebid-server/v3/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v3/stored_responses" + "github.com/prebid/prebid-server/v3/util/iputil" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" ) const jsonFileExtension string = ".json" +func TestMain(m *testing.M) { + jsoniter.RegisterExtension(&jsonutil.RawMessageExtension{}) + os.Exit(m.Run()) +} + func TestJsonSampleRequests(t *testing.T) { testSuites := []struct { description string @@ -65,16 +71,20 @@ func TestJsonSampleRequests(t *testing.T) { "invalid-native", }, { - "Makes sure we handle (default) aliased bidders properly", + "Makes sure we handle aliased bidders properly", "aliased", }, + { + "Makes sure we handle alternate bidder codes properly", + "alternate-bidder-code", + }, { "Asserts we return 500s on requests referencing accounts with malformed configs.", "account-malformed", }, { - "Asserts we return 503s on requests with blacklisted accounts and apps.", - "blacklisted", + "Asserts we return 503s on requests with blocked apps.", + "blocked", }, { "Assert that requests that come with no user id nor app id return error if the `AccountRequired` field in the `config.Configuration` structure is set to true", @@ -159,18 +169,37 @@ func runJsonBasedTest(t *testing.T, filename, desc string) { // Build endpoint for testing. If no error, run test case cfg := &config.Configuration{MaxRequestSize: maxSize} if test.Config != nil { - cfg.BlacklistedApps = test.Config.BlacklistedApps - cfg.BlacklistedAppMap = test.Config.getBlacklistedAppMap() + cfg.BlockedApps = test.Config.BlockedApps + cfg.BlockedAppsLookup = test.Config.getBlockedAppLookup() cfg.AccountRequired = test.Config.AccountRequired } cfg.MarshalAccountDefaults() test.endpointType = OPENRTB_ENDPOINT - auctionEndpointHandler, _, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg) + auctionEndpointHandler, ex, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg) if assert.NoError(t, err) { assert.NotPanics(t, func() { runEndToEndTest(t, auctionEndpointHandler, test, fileData, filename) }, filename) } + if test.ExpectedValidatedBidReq != nil { + // compare as json to ignore whitespace and ext field ordering + actualJson, err := jsonutil.Marshal(ex.actualValidatedBidReq) + if assert.NoError(t, err, "Error converting actual bid request to json. Test file: %s", filename) { + assert.JSONEq(t, string(test.ExpectedValidatedBidReq), string(actualJson), "Not the expected validated request. Test file: %s", filename) + } + } + if test.ExpectedMockBidderRequests != nil { + for bidder, req := range test.ExpectedMockBidderRequests { + a, ok := ex.adapters[openrtb_ext.BidderName(bidder)] + if !ok { + t.Fatalf("Unexpected bidder %s has an expected mock bidder request. Test file: %s", bidder, filename) + } + aa := a.(*exchange.BidderAdapter) + ma := aa.Bidder.(*mockAdapter) + assert.JSONEq(t, string(req), string(ma.requestData[0]), "Not the expected mock bidder request for bidder %s. Test file: %s", bidder, filename) + } + } + // Close servers regardless if the test case was run or not for _, mockBidServer := range mockBidServers { mockBidServer.Close() @@ -443,7 +472,7 @@ func TestExplicitUserId(t *testing.T) { endpoint, _ := NewEndpoint( fakeUUIDGenerator{}, ex, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, @@ -501,7 +530,7 @@ func doBadAliasRequest(t *testing.T, filename string, expectMsg string) { endpoint, _ := NewEndpoint( fakeUUIDGenerator{}, &nobidExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(bidderMap, disabledBidders, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, @@ -556,32 +585,7 @@ func TestNilExchange(t *testing.T) { _, err := NewEndpoint( fakeUUIDGenerator{}, nil, - mockBidderParamValidator{}, - empty_fetcher.EmptyFetcher{}, - empty_fetcher.EmptyFetcher{}, - &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.NilMetricsEngine{}, - analyticsBuild.New(&config.Analytics{}), map[string]string{}, - []byte{}, - openrtb_ext.BuildBidderMap(), - empty_fetcher.EmptyFetcher{}, - hooks.EmptyPlanBuilder{}, - nil, - ) - - if err == nil { - t.Errorf("NewEndpoint should return an error when given a nil Exchange.") - } -} - -// TestNilValidator makes sure we fail when given nil for the BidderParamValidator. -func TestNilValidator(t *testing.T) { - // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. - // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. - _, err := NewEndpoint( - fakeUUIDGenerator{}, - &nobidExchange{}, - nil, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, @@ -596,7 +600,7 @@ func TestNilValidator(t *testing.T) { ) if err == nil { - t.Errorf("NewEndpoint should return an error when given a nil BidderParamValidator.") + t.Errorf("NewEndpoint should return an error when given a nil Exchange.") } } @@ -607,7 +611,7 @@ func TestExchangeError(t *testing.T) { endpoint, _ := NewEndpoint( fakeUUIDGenerator{}, &brokenExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, @@ -734,7 +738,7 @@ func TestImplicitIPsEndToEnd(t *testing.T) { endpoint, _ := NewEndpoint( fakeUUIDGenerator{}, exchange, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, @@ -934,7 +938,7 @@ func TestImplicitDNTEndToEnd(t *testing.T) { endpoint, _ := NewEndpoint( fakeUUIDGenerator{}, exchange, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, @@ -1170,7 +1174,7 @@ func TestStoredRequests(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, &nobidExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -1620,7 +1624,7 @@ func TestValidateRequest(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, &nobidExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -1644,6 +1648,8 @@ func TestValidateRequest(t *testing.T) { description string givenIsAmp bool givenRequestWrapper *openrtb_ext.RequestWrapper + givenHttpRequest *http.Request + givenAccount *config.Account expectedErrorList []error expectedChannelObject *openrtb_ext.ExtRequestPrebidChannel }{ @@ -1864,7 +1870,7 @@ func TestValidateRequest(t *testing.T) { } for _, test := range testCases { - errorList := deps.validateRequest(test.givenRequestWrapper, test.givenIsAmp, false, nil, false) + errorList := deps.validateRequest(test.givenAccount, test.givenHttpRequest, test.givenRequestWrapper, test.givenIsAmp, false, nil, false) assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description) if len(errorList) == 0 { @@ -1933,9 +1939,13 @@ func TestValidateRequestExt(t *testing.T) { givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"bids":{},"vastxml":{}}}}`), }, { - description: "prebid targeting", // test integration with validateTargeting - givenRequestExt: json.RawMessage(`{"prebid":{"targeting":{}}}`), - expectedErrors: []string{"ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support"}, + description: "prebid price granularity invalid", + givenRequestExt: json.RawMessage(`{"prebid":{"targeting":{"pricegranularity":{"precision":-1,"ranges":[{"min":0,"max":20,"increment":0.1}]}}}}`), + expectedErrors: []string{"Price granularity error: precision must be non-negative"}, + }, + { + description: "prebid native media type price granualrity valid", + givenRequestExt: json.RawMessage(`{"prebid":{"targeting":{"mediatypepricegranularity":{"native":{"precision":3,"ranges":[{"max":20,"increment":4.5}]}}}}}`), }, { description: "valid multibid", @@ -1977,74 +1987,8 @@ func TestValidateTargeting(t *testing.T) { expectedError: nil, }, { - name: "empty", - givenTargeting: &openrtb_ext.ExtRequestTargeting{}, - expectedError: errors.New("ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support"), - }, - { - name: "includewinners nil, includebidderkeys false", - givenTargeting: &openrtb_ext.ExtRequestTargeting{ - IncludeBidderKeys: ptrutil.ToPtr(false), - }, - expectedError: errors.New("ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support"), - }, - { - name: "includewinners nil, includebidderkeys true", - givenTargeting: &openrtb_ext.ExtRequestTargeting{ - IncludeBidderKeys: ptrutil.ToPtr(true), - }, - expectedError: nil, - }, - { - name: "includewinners false, includebidderkeys nil", - givenTargeting: &openrtb_ext.ExtRequestTargeting{ - IncludeWinners: ptrutil.ToPtr(false), - }, - expectedError: errors.New("ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support"), - }, - { - name: "includewinners true, includebidderkeys nil", - givenTargeting: &openrtb_ext.ExtRequestTargeting{ - IncludeWinners: ptrutil.ToPtr(true), - }, - expectedError: nil, - }, - { - name: "all false", - givenTargeting: &openrtb_ext.ExtRequestTargeting{ - IncludeWinners: ptrutil.ToPtr(false), - IncludeBidderKeys: ptrutil.ToPtr(false), - }, - expectedError: errors.New("ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support"), - }, - { - name: "includewinners false, includebidderkeys true", - givenTargeting: &openrtb_ext.ExtRequestTargeting{ - IncludeWinners: ptrutil.ToPtr(false), - IncludeBidderKeys: ptrutil.ToPtr(true), - }, - expectedError: nil, - }, - { - name: "includewinners false, includebidderkeys true", + name: "pricegranularity-ranges-out-of-order", givenTargeting: &openrtb_ext.ExtRequestTargeting{ - IncludeWinners: ptrutil.ToPtr(true), - IncludeBidderKeys: ptrutil.ToPtr(false), - }, - expectedError: nil, - }, - { - name: "includewinners true, includebidderkeys true", - givenTargeting: &openrtb_ext.ExtRequestTargeting{ - IncludeWinners: ptrutil.ToPtr(true), - IncludeBidderKeys: ptrutil.ToPtr(true), - }, - expectedError: nil, - }, - { - name: "price granularity ranges out of order", - givenTargeting: &openrtb_ext.ExtRequestTargeting{ - IncludeWinners: ptrutil.ToPtr(true), PriceGranularity: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -2056,10 +2000,16 @@ func TestValidateTargeting(t *testing.T) { expectedError: errors.New(`Price granularity error: range list must be ordered with increasing "max"`), }, { - name: "media type price granularity video correct", + name: "mediatypepricegranularity-nil", + givenTargeting: &openrtb_ext.ExtRequestTargeting{ + MediaTypePriceGranularity: nil, + }, + expectedError: nil, + }, + { + name: "mediatypepricegranularity-video-ok", givenTargeting: &openrtb_ext.ExtRequestTargeting{ - IncludeWinners: ptrutil.ToPtr(true), - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Video: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -2071,10 +2021,9 @@ func TestValidateTargeting(t *testing.T) { expectedError: nil, }, { - name: "media type price granularity banner correct", + name: "mediatypepricegranularity-banner-ok", givenTargeting: &openrtb_ext.ExtRequestTargeting{ - IncludeWinners: ptrutil.ToPtr(true), - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Banner: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -2086,10 +2035,9 @@ func TestValidateTargeting(t *testing.T) { expectedError: nil, }, { - name: "media type price granularity native correct", + name: "mediatypepricegranularity-native-ok", givenTargeting: &openrtb_ext.ExtRequestTargeting{ - IncludeWinners: ptrutil.ToPtr(true), - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Native: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -2101,10 +2049,9 @@ func TestValidateTargeting(t *testing.T) { expectedError: nil, }, { - name: "media type price granularity video and banner correct", + name: "mediatypepricegranularity-video+banner-ok", givenTargeting: &openrtb_ext.ExtRequestTargeting{ - IncludeWinners: ptrutil.ToPtr(true), - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Banner: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -2122,10 +2069,9 @@ func TestValidateTargeting(t *testing.T) { expectedError: nil, }, { - name: "media type price granularity video incorrect", + name: "mediatypepricegranularity-video-invalid", givenTargeting: &openrtb_ext.ExtRequestTargeting{ - IncludeWinners: ptrutil.ToPtr(true), - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Video: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -2137,10 +2083,9 @@ func TestValidateTargeting(t *testing.T) { expectedError: errors.New("Price granularity error: increment must be a nonzero positive number"), }, { - name: "media type price granularity banner incorrect", + name: "mediatypepricegranularity-banner-invalid", givenTargeting: &openrtb_ext.ExtRequestTargeting{ - IncludeWinners: ptrutil.ToPtr(true), - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Banner: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -2152,10 +2097,9 @@ func TestValidateTargeting(t *testing.T) { expectedError: errors.New("Price granularity error: range list must be ordered with increasing \"max\""), }, { - name: "media type price granularity native incorrect", + name: "mediatypepricegranularity-native-invalid", givenTargeting: &openrtb_ext.ExtRequestTargeting{ - IncludeWinners: ptrutil.ToPtr(true), - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Native: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -2167,10 +2111,9 @@ func TestValidateTargeting(t *testing.T) { expectedError: errors.New("Price granularity error: range list must be ordered with increasing \"max\""), }, { - name: "media type price granularity video correct and banner incorrect", + name: "mediatypepricegranularity-video-ok-banner-invalid", givenTargeting: &openrtb_ext.ExtRequestTargeting{ - IncludeWinners: ptrutil.ToPtr(true), - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Banner: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -2188,10 +2131,9 @@ func TestValidateTargeting(t *testing.T) { expectedError: errors.New("Price granularity error: range list must be ordered with increasing \"max\""), }, { - name: "media type price granularity native incorrect and banner correct", + name: "mediatypepricegranularity-native-invalid-banner-ok", givenTargeting: &openrtb_ext.ExtRequestTargeting{ - IncludeWinners: ptrutil.ToPtr(true), - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Native: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(2), Ranges: []openrtb_ext.GranularityRange{ @@ -2212,7 +2154,7 @@ func TestValidateTargeting(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expectedError, validateTargeting(tc.givenTargeting), "Targeting") + assert.Equal(t, tc.expectedError, validateTargeting(tc.givenTargeting)) }) } } @@ -2400,7 +2342,7 @@ func TestSetIntegrationType(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, &nobidExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -2467,7 +2409,7 @@ func TestStoredRequestGenerateUuid(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{id: "foo", err: nil}, &nobidExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -2572,7 +2514,7 @@ func TestOversizedRequest(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, &nobidExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -2612,7 +2554,7 @@ func TestRequestSizeEdgeCase(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, &nobidExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -2651,7 +2593,7 @@ func TestNoEncoding(t *testing.T) { endpoint, _ := NewEndpoint( fakeUUIDGenerator{}, &mockExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, @@ -2736,7 +2678,7 @@ func TestContentType(t *testing.T) { endpoint, _ := NewEndpoint( fakeUUIDGenerator{}, &mockExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, @@ -2758,241 +2700,6 @@ func TestContentType(t *testing.T) { } } -func TestValidateImpExt(t *testing.T) { - type testCase struct { - description string - impExt json.RawMessage - expectedImpExt string - expectedErrs []error - } - testGroups := []struct { - description string - testCases []testCase - }{ - { - "Empty", - []testCase{ - { - description: "Empty", - impExt: nil, - expectedImpExt: "", - expectedErrs: []error{errors.New("request.imp[0].ext is required")}, - }, - }, - }, - { - "Unknown bidder tests", - []testCase{ - { - description: "Unknown Bidder only", - impExt: json.RawMessage(`{"unknownbidder":{"placement_id":555}}`), - expectedImpExt: `{"unknownbidder":{"placement_id":555}}`, - expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, - }, - { - description: "Unknown Prebid Ext Bidder only", - impExt: json.RawMessage(`{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}}}`), - expectedImpExt: `{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}}}`, - expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, - }, - { - description: "Unknown Prebid Ext Bidder + First Party Data Context", - impExt: json.RawMessage(`{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`), - expectedImpExt: `{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, - expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, - }, - { - description: "Unknown Bidder + First Party Data Context", - impExt: json.RawMessage(`{"unknownbidder":{"placement_id":555} ,"context":{"data":{"keywords":"prebid server example"}}}`), - expectedImpExt: `{"unknownbidder":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`, - expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, - }, - { - description: "Unknown Bidder + Disabled Bidder", - impExt: json.RawMessage(`{"unknownbidder":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`), - expectedImpExt: `{"unknownbidder":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`, - expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, - }, - { - description: "Unknown Bidder + Disabled Prebid Ext Bidder", - impExt: json.RawMessage(`{"unknownbidder":{"placement_id":555},"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`), - expectedImpExt: `{"unknownbidder":{"placement_id":555},"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`, - expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, - }, - }, - }, - { - "Disabled bidder tests", - []testCase{ - { - description: "Disabled Bidder", - impExt: json.RawMessage(`{"disabledbidder":{"foo":"bar"}}`), - expectedImpExt: `{"disabledbidder":{"foo":"bar"}}`, - expectedErrs: []error{ - &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, - errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), - }, - // if only bidder(s) found in request.imp[x].ext.{biddername} or request.imp[x].ext.prebid.bidder.{biddername} are disabled, return error - }, - { - description: "Disabled Prebid Ext Bidder", - impExt: json.RawMessage(`{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`), - expectedImpExt: `{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`, - expectedErrs: []error{ - &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, - errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), - }, - }, - { - description: "Disabled Bidder + First Party Data Context", - impExt: json.RawMessage(`{"disabledbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`), - expectedImpExt: `{"disabledbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`, - expectedErrs: []error{ - &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, - errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), - }, - }, - { - description: "Disabled Prebid Ext Bidder + First Party Data Context", - impExt: json.RawMessage(`{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`), - expectedImpExt: `{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`, - expectedErrs: []error{ - &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, - errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), - }, - }, - }, - }, - { - "First Party only", - []testCase{ - { - description: "First Party Data Context", - impExt: json.RawMessage(`{"context":{"data":{"keywords":"prebid server example"}}}`), - expectedImpExt: `{"context":{"data":{"keywords":"prebid server example"}}}`, - expectedErrs: []error{ - errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), - }, - }, - }, - }, - { - "Valid bidder tests", - []testCase{ - { - description: "Valid bidder root ext", - impExt: json.RawMessage(`{"appnexus":{"placement_id":555}}`), - expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`, - expectedErrs: []error{}, - }, - { - description: "Valid bidder in prebid field", - impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`), - expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`, - expectedErrs: []error{}, - }, - { - description: "Valid Bidder + First Party Data Context", - impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`), - expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, - expectedErrs: []error{}, - }, - { - description: "Valid Prebid Ext Bidder + First Party Data Context", - impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555}}} ,"context":{"data":{"keywords":"prebid server example"}}}`), - expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, - expectedErrs: []error{}, - }, - { - description: "Valid Bidder + Unknown Bidder", - impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"unknownbidder":{"placement_id":555}}`), - expectedImpExt: `{"appnexus":{"placement_id":555},"unknownbidder":{"placement_id":555}}`, - expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, - }, - { - description: "Valid Bidder + Disabled Bidder", - impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`), - expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`, - expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}}, - }, - { - description: "Valid Bidder + Disabled Bidder + First Party Data Context", - impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`), - expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, - expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}}, - }, - { - description: "Valid Bidder + Disabled Bidder + Unknown Bidder + First Party Data Context", - impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`), - expectedImpExt: `{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`, - expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, - }, - { - description: "Valid Prebid Ext Bidder + Disabled Bidder Ext", - impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}}}`), - expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555}}}}`, - expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}}, - }, - { - description: "Valid Prebid Ext Bidder + Disabled Ext Bidder + First Party Data Context", - impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`), - expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555}}},"context":{"data":{"keywords":"prebid server example"}}}`, - expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}}, - }, - { - description: "Valid Prebid Ext Bidder + Disabled Ext Bidder + Unknown Ext + First Party Data Context", - impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`), - expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, - expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, - }, - }, - }, - } - - deps := &endpointDeps{ - fakeUUIDGenerator{}, - &nobidExchange{}, - mockBidderParamValidator{}, - &mockStoredReqFetcher{}, - empty_fetcher.EmptyFetcher{}, - empty_fetcher.EmptyFetcher{}, - &config.Configuration{MaxRequestSize: int64(8096)}, - &metricsConfig.NilMetricsEngine{}, - analyticsBuild.New(&config.Analytics{}), - map[string]string{"disabledbidder": "The bidder 'disabledbidder' has been disabled."}, - false, - []byte{}, - openrtb_ext.BuildBidderMap(), - nil, - nil, - hardcodedResponseIPValidator{response: true}, - empty_fetcher.EmptyFetcher{}, - hooks.EmptyPlanBuilder{}, - nil, - openrtb_ext.NormalizeBidderName, - } - - for _, group := range testGroups { - for _, test := range group.testCases { - t.Run(test.description, func(t *testing.T) { - imp := &openrtb2.Imp{Ext: test.impExt} - impWrapper := &openrtb_ext.ImpWrapper{Imp: imp} - - errs := deps.validateImpExt(impWrapper, nil, 0, false, nil) - - assert.NoError(t, impWrapper.RebuildImp(), test.description+":rebuild_imp") - - if len(test.expectedImpExt) > 0 { - assert.JSONEq(t, test.expectedImpExt, string(imp.Ext), "imp.ext JSON does not match expected. Test: %s. %s\n", group.description, test.description) - } else { - assert.Empty(t, imp.Ext, "imp.ext expected to be empty but was: %s. Test: %s. %s\n", string(imp.Ext), group.description, test.description) - } - assert.Equal(t, test.expectedErrs, errs, "errs slice does not match expected. Test: %s. %s\n", group.description, test.description) - }) - } - } -} - func validRequest(t *testing.T, filename string) string { requestData, err := os.ReadFile("sample-requests/valid-whole/supplementary/" + filename) if err != nil { @@ -3008,7 +2715,7 @@ func TestCurrencyTrunc(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, &nobidExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -3047,7 +2754,7 @@ func TestCurrencyTrunc(t *testing.T) { Cur: []string{"USD", "EUR"}, } - errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false) + errL := deps.validateRequest(nil, nil, &openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false) expectedError := errortypes.Warning{Message: "A prebid request can only process one currency. Taking the first currency in the list, USD, as the active currency"} assert.ElementsMatch(t, errL, []error{&expectedError}) @@ -3057,7 +2764,7 @@ func TestCCPAInvalid(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, &nobidExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -3094,11 +2801,11 @@ func TestCCPAInvalid(t *testing.T) { ID: "anySiteID", }, Regs: &openrtb2.Regs{ - Ext: json.RawMessage(`{"us_privacy": "invalid by length"}`), + USPrivacy: "invalid by length", }, } - errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false) + errL := deps.validateRequest(nil, nil, &openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false) expectedWarning := errortypes.Warning{ Message: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)", @@ -3110,7 +2817,7 @@ func TestNoSaleInvalid(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, &nobidExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -3152,7 +2859,7 @@ func TestNoSaleInvalid(t *testing.T) { Ext: json.RawMessage(`{"prebid": {"nosale": ["*", "appnexus"]} }`), } - errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false) + errL := deps.validateRequest(nil, nil, &openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false) expectedError := errors.New("request.ext.prebid.nosale is invalid: can only specify all bidders if no other bidders are provided") assert.ElementsMatch(t, errL, []error{expectedError}) @@ -3166,7 +2873,7 @@ func TestValidateSourceTID(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, &nobidExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -3204,7 +2911,7 @@ func TestValidateSourceTID(t *testing.T) { }, } - deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false) + deps.validateRequest(nil, nil, &openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false) assert.NotEmpty(t, req.Source.TID, "Expected req.Source.TID to be filled with a randomly generated UID") } @@ -3212,7 +2919,7 @@ func TestSChainInvalid(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, &nobidExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -3251,140 +2958,12 @@ func TestSChainInvalid(t *testing.T) { Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`), } - errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false) + errL := deps.validateRequest(nil, nil, &openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false) expectedError := errors.New("request.ext.prebid.schains contains multiple schains for bidder appnexus; it must contain no more than one per bidder.") assert.ElementsMatch(t, errL, []error{expectedError}) } -func TestMapSChains(t *testing.T) { - const seller1SChain string = `"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}` - const seller2SChain string = `"schain":{"complete":2,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":2}],"ver":"2.0"}` - - seller1SChainUnpacked := openrtb2.SupplyChain{ - Complete: 1, - Nodes: []openrtb2.SupplyChainNode{{ - ASI: "directseller1.com", - SID: "00001", - RID: "BidRequest1", - HP: openrtb2.Int8Ptr(1), - }}, - Ver: "1.0", - } - - tests := []struct { - description string - bidRequest openrtb2.BidRequest - wantReqExtSChain *openrtb2.SupplyChain - wantSourceExtSChain *openrtb2.SupplyChain - wantError bool - }{ - { - description: "invalid req.ext", - bidRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":invalid}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{}`), - }, - }, - wantError: true, - }, - { - description: "invalid source.ext", - bidRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{"schain":invalid}}`), - }, - }, - wantError: true, - }, - { - description: "req.ext.prebid.schains, req.source.ext.schain and req.ext.schain are nil", - bidRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{}`), - }, - }, - wantReqExtSChain: nil, - wantSourceExtSChain: nil, - }, - { - description: "req.ext.prebid.schains is not nil", - bidRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{}`), - }, - }, - wantReqExtSChain: nil, - wantSourceExtSChain: nil, - }, - { - description: "req.source.ext is not nil", - bidRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller1SChain + `}`), - }, - }, - wantReqExtSChain: nil, - wantSourceExtSChain: &seller1SChainUnpacked, - }, - { - description: "req.ext.schain is not nil", - bidRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{` + seller1SChain + `}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{}`), - }, - }, - wantReqExtSChain: nil, - wantSourceExtSChain: &seller1SChainUnpacked, - }, - { - description: "req.source.ext.schain and req.ext.schain are not nil", - bidRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{` + seller2SChain + `}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller1SChain + `}`), - }, - }, - wantReqExtSChain: nil, - wantSourceExtSChain: &seller1SChainUnpacked, - }, - } - - for _, test := range tests { - reqWrapper := openrtb_ext.RequestWrapper{ - BidRequest: &test.bidRequest, - } - - err := mapSChains(&reqWrapper) - - if test.wantError { - assert.NotNil(t, err, test.description) - } else { - assert.Nil(t, err, test.description) - - reqExt, err := reqWrapper.GetRequestExt() - if err != nil { - assert.Fail(t, "Error getting request ext from wrapper", test.description) - } - reqExtSChain := reqExt.GetSChain() - assert.Equal(t, test.wantReqExtSChain, reqExtSChain, test.description) - - sourceExt, err := reqWrapper.GetSourceExt() - if err != nil { - assert.Fail(t, "Error getting source ext from wrapper", test.description) - } - sourceExtSChain := sourceExt.GetSChain() - assert.Equal(t, test.wantSourceExtSChain, sourceExtSChain, test.description) - } - } -} - func TestSearchAccountID(t *testing.T) { // Correctness for lookup within Publisher object left to TestGetAccountID // This however tests the expected lookup paths in outer site, app and dooh @@ -3781,7 +3360,7 @@ func TestEidPermissionsInvalid(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, &nobidExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -3820,7 +3399,7 @@ func TestEidPermissionsInvalid(t *testing.T) { Ext: json.RawMessage(`{"prebid": {"data": {"eidpermissions": [{"source":"a", "bidders":[]}]} } }`), } - errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false) + errL := deps.validateRequest(nil, nil, &openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false) expectedError := errors.New(`request.ext.prebid.data.eidpermissions[0] missing or empty required field: "bidders"`) assert.ElementsMatch(t, errL, []error{expectedError}) @@ -3831,51 +3410,51 @@ func TestValidateEidPermissions(t *testing.T) { knownAliases := map[string]string{"b": "b"} testCases := []struct { - description string + name string request *openrtb_ext.ExtRequest expectedError error }{ { - description: "Valid - Empty ext", + name: "valid-empty-ext", request: &openrtb_ext.ExtRequest{}, expectedError: nil, }, { - description: "Valid - Nil ext.prebid.data", + name: "valid-nil-ext.prebid.data", request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{}}, expectedError: nil, }, { - description: "Valid - Empty ext.prebid.data", + name: "valid-empty-ext.prebid.data", request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{}}}, expectedError: nil, }, { - description: "Valid - Nil ext.prebid.data.eidpermissions", + name: "valid-nil-ext.prebid.data.eidpermissions", request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: nil}}}, expectedError: nil, }, { - description: "Valid - None", + name: "valid-none", request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{}}}}, expectedError: nil, }, { - description: "Valid - One", + name: "valid-one", request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "sourceA", Bidders: []string{"a"}}, }}}}, expectedError: nil, }, { - description: "Valid - One - Case Insensitive", + name: "valid-one-case-insensitive", request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "sourceA", Bidders: []string{"A"}}, }}}}, expectedError: nil, }, { - description: "Valid - Many", + name: "valid-many", request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "sourceA", Bidders: []string{"a"}}, {Source: "sourceB", Bidders: []string{"a"}}, @@ -3883,7 +3462,7 @@ func TestValidateEidPermissions(t *testing.T) { expectedError: nil, }, { - description: "Invalid - Missing Source", + name: "invalid-missing-source", request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "sourceA", Bidders: []string{"a"}}, {Bidders: []string{"a"}}, @@ -3891,7 +3470,7 @@ func TestValidateEidPermissions(t *testing.T) { expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] missing required field: "source"`), }, { - description: "Invalid - Duplicate Source", + name: "invalid-duplicate-source", request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "sourceA", Bidders: []string{"a"}}, {Source: "sourceA", Bidders: []string{"a"}}, @@ -3899,7 +3478,7 @@ func TestValidateEidPermissions(t *testing.T) { expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] duplicate entry with field: "source"`), }, { - description: "Invalid - Missing Bidders - Nil", + name: "invalid-missing-bidders-nil", request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "sourceA", Bidders: []string{"a"}}, {Source: "sourceB"}, @@ -3907,7 +3486,7 @@ func TestValidateEidPermissions(t *testing.T) { expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] missing or empty required field: "bidders"`), }, { - description: "Invalid - Missing Bidders - Empty", + name: "invalid-missing-bidders-empty", request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "sourceA", Bidders: []string{"a"}}, {Source: "sourceB", Bidders: []string{}}, @@ -3915,7 +3494,7 @@ func TestValidateEidPermissions(t *testing.T) { expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] missing or empty required field: "bidders"`), }, { - description: "Invalid - Invalid Bidders", + name: "invalid-invalid-bidders", request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "sourceA", Bidders: []string{"a"}}, {Source: "sourceB", Bidders: []string{"z"}}, @@ -3923,7 +3502,7 @@ func TestValidateEidPermissions(t *testing.T) { expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] contains unrecognized bidder "z"`), }, { - description: "Valid - Alias Case Sensitive", + name: "invalid-alias-case-sensitive", request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "sourceA", Bidders: []string{"B"}}, }}}}, @@ -3933,8 +3512,10 @@ func TestValidateEidPermissions(t *testing.T) { endpoint := &endpointDeps{bidderMap: knownBidders, normalizeBidderName: fakeNormalizeBidderName} for _, test := range testCases { - result := endpoint.validateEidPermissions(test.request.Prebid.Data, knownAliases) - assert.Equal(t, test.expectedError, result, test.description) + t.Run(test.name, func(t *testing.T) { + result := endpoint.validateEidPermissions(test.request.Prebid.Data, knownAliases) + assert.Equal(t, test.expectedError, result) + }) } } @@ -4059,7 +3640,7 @@ func TestIOS14EndToEnd(t *testing.T) { endpoint, _ := NewEndpoint( fakeUUIDGenerator{}, exchange, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, @@ -4121,7 +3702,7 @@ func TestAuctionWarnings(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, &warningsCheckExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -4168,7 +3749,7 @@ func TestParseRequestParseImpInfoError(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, &warningsCheckExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -4249,7 +3830,7 @@ func TestParseGzipedRequest(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, &warningsCheckExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -4301,448 +3882,56 @@ func TestParseGzipedRequest(t *testing.T) { } } -func TestValidateNativeContextTypes(t *testing.T) { - impIndex := 4 - +func TestAuctionResponseHeaders(t *testing.T) { testCases := []struct { - description string - givenContextType native1.ContextType - givenSubType native1.ContextSubType - expectedError string + description string + httpRequest *http.Request + expectedStatus int + expectedHeaders func(http.Header) }{ { - description: "No Types Specified", - givenContextType: 0, - givenSubType: 0, - expectedError: "", - }, - { - description: "All Types Exchange Specific", - givenContextType: 500, - givenSubType: 500, - expectedError: "", - }, - { - description: "Context Type Known Value - Sub Type Unspecified", - givenContextType: 1, - givenSubType: 0, - expectedError: "", - }, - { - description: "Context Type Negative", - givenContextType: -1, - givenSubType: 0, - expectedError: "request.imp[4].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", - }, - { - description: "Context Type Just Above Range", - givenContextType: 4, // Range is currently 1-3 - givenSubType: 0, - expectedError: "request.imp[4].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", - }, - { - description: "Sub Type Negative", - givenContextType: 1, - givenSubType: -1, - expectedError: "request.imp[4].native.request.contextsubtype value can't be less than 0. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", - }, - { - description: "Content - Sub Type Just Below Range", - givenContextType: 1, // Content constant - givenSubType: 9, // Content range is currently 10-15 - expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", - }, - { - description: "Content - Sub Type In Range", - givenContextType: 1, // Content constant - givenSubType: 10, // Content range is currently 10-15 - expectedError: "", - }, - { - description: "Content - Sub Type In Range - Context Type Exchange Specific Boundary", - givenContextType: 500, - givenSubType: 10, // Content range is currently 10-15 - expectedError: "", - }, - { - description: "Content - Sub Type In Range - Context Type Exchange Specific Boundary + 1", - givenContextType: 501, - givenSubType: 10, // Content range is currently 10-15 - expectedError: "", - }, - { - description: "Content - Sub Type Just Above Range", - givenContextType: 1, // Content constant - givenSubType: 16, // Content range is currently 10-15 - expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", - }, - { - description: "Content - Sub Type Exchange Specific Boundary", - givenContextType: 1, // Content constant - givenSubType: 500, - expectedError: "", - }, - { - description: "Content - Sub Type Exchange Specific Boundary + 1", - givenContextType: 1, // Content constant - givenSubType: 501, - expectedError: "", - }, - { - description: "Content - Invalid Context Type", - givenContextType: 2, // Not content constant - givenSubType: 10, // Content range is currently 10-15 - expectedError: "request.imp[4].native.request.context is 2, but contextsubtype is 10. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", - }, - { - description: "Social - Sub Type Just Below Range", - givenContextType: 2, // Social constant - givenSubType: 19, // Social range is currently 20-22 - expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", - }, - { - description: "Social - Sub Type In Range", - givenContextType: 2, // Social constant - givenSubType: 20, // Social range is currently 20-22 - expectedError: "", - }, - { - description: "Social - Sub Type In Range - Context Type Exchange Specific Boundary", - givenContextType: 500, - givenSubType: 20, // Social range is currently 20-22 - expectedError: "", + description: "Success Response", + httpRequest: httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))), + expectedStatus: 200, + expectedHeaders: func(h http.Header) { + h.Set("X-Prebid", "pbs-go/unknown") + h.Set("Content-Type", "application/json") + }, }, { - description: "Social - Sub Type In Range - Context Type Exchange Specific Boundary + 1", - givenContextType: 501, - givenSubType: 20, // Social range is currently 20-22 - expectedError: "", + description: "Failure Response", + httpRequest: httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader("{}")), + expectedStatus: 400, + expectedHeaders: func(h http.Header) { + h.Set("X-Prebid", "pbs-go/unknown") + }, }, { - description: "Social - Sub Type Just Above Range", - givenContextType: 2, // Social constant - givenSubType: 23, // Social range is currently 20-22 - expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + description: "Success Response with Chrome BrowsingTopicsHeader", + httpRequest: func() *http.Request { + httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) + httpReq.Header.Add(secBrowsingTopics, "sample-value") + return httpReq + }(), + expectedStatus: 200, + expectedHeaders: func(h http.Header) { + h.Set("X-Prebid", "pbs-go/unknown") + h.Set("Content-Type", "application/json") + h.Set("Observe-Browsing-Topics", "?1") + }, }, { - description: "Social - Sub Type Exchange Specific Boundary", - givenContextType: 2, // Social constant - givenSubType: 500, - expectedError: "", - }, - { - description: "Social - Sub Type Exchange Specific Boundary + 1", - givenContextType: 2, // Social constant - givenSubType: 501, - expectedError: "", - }, - { - description: "Social - Invalid Context Type", - givenContextType: 3, // Not social constant - givenSubType: 20, // Social range is currently 20-22 - expectedError: "request.imp[4].native.request.context is 3, but contextsubtype is 20. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", - }, - { - description: "Product - Sub Type Just Below Range", - givenContextType: 3, // Product constant - givenSubType: 29, // Product range is currently 30-32 - expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", - }, - { - description: "Product - Sub Type In Range", - givenContextType: 3, // Product constant - givenSubType: 30, // Product range is currently 30-32 - expectedError: "", - }, - { - description: "Product - Sub Type In Range - Context Type Exchange Specific Boundary", - givenContextType: 500, - givenSubType: 30, // Product range is currently 30-32 - expectedError: "", - }, - { - description: "Product - Sub Type In Range - Context Type Exchange Specific Boundary + 1", - givenContextType: 501, - givenSubType: 30, // Product range is currently 30-32 - expectedError: "", - }, - { - description: "Product - Sub Type Just Above Range", - givenContextType: 3, // Product constant - givenSubType: 33, // Product range is currently 30-32 - expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", - }, - { - description: "Product - Sub Type Exchange Specific Boundary", - givenContextType: 3, // Product constant - givenSubType: 500, - expectedError: "", - }, - { - description: "Product - Sub Type Exchange Specific Boundary + 1", - givenContextType: 3, // Product constant - givenSubType: 501, - expectedError: "", - }, - { - description: "Product - Invalid Context Type", - givenContextType: 1, // Not product constant - givenSubType: 30, // Product range is currently 30-32 - expectedError: "request.imp[4].native.request.context is 1, but contextsubtype is 30. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", - }, - } - - for _, test := range testCases { - err := validateNativeContextTypes(test.givenContextType, test.givenSubType, impIndex) - if test.expectedError == "" { - assert.NoError(t, err, test.description) - } else { - assert.EqualError(t, err, test.expectedError, test.description) - } - } -} - -func TestValidateNativePlacementType(t *testing.T) { - impIndex := 4 - - testCases := []struct { - description string - givenPlacementType native1.PlacementType - expectedError string - }{ - { - description: "Not Specified", - givenPlacementType: 0, - expectedError: "", - }, - { - description: "Known Value", - givenPlacementType: 1, // Range is currently 1-4 - expectedError: "", - }, - { - description: "Exchange Specific - Boundary", - givenPlacementType: 500, - expectedError: "", - }, - { - description: "Exchange Specific - Boundary + 1", - givenPlacementType: 501, - expectedError: "", - }, - { - description: "Negative", - givenPlacementType: -1, - expectedError: "request.imp[4].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", - }, - { - description: "Just Above Range", - givenPlacementType: 5, // Range is currently 1-4 - expectedError: "request.imp[4].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", - }, - } - - for _, test := range testCases { - err := validateNativePlacementType(test.givenPlacementType, impIndex) - if test.expectedError == "" { - assert.NoError(t, err, test.description) - } else { - assert.EqualError(t, err, test.expectedError, test.description) - } - } -} - -func TestValidateNativeEventTracker(t *testing.T) { - impIndex := 4 - eventIndex := 8 - - testCases := []struct { - description string - givenEvent nativeRequests.EventTracker - expectedError string - }{ - { - description: "Valid", - givenEvent: nativeRequests.EventTracker{ - Event: 1, - Methods: []native1.EventTrackingMethod{1}, - }, - expectedError: "", - }, - { - description: "Event - Exchange Specific - Boundary", - givenEvent: nativeRequests.EventTracker{ - Event: 500, - Methods: []native1.EventTrackingMethod{1}, - }, - expectedError: "", - }, - { - description: "Event - Exchange Specific - Boundary + 1", - givenEvent: nativeRequests.EventTracker{ - Event: 501, - Methods: []native1.EventTrackingMethod{1}, - }, - expectedError: "", - }, - { - description: "Event - Negative", - givenEvent: nativeRequests.EventTracker{ - Event: -1, - Methods: []native1.EventTrackingMethod{1}, - }, - expectedError: "request.imp[4].native.request.eventtrackers[8].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", - }, - { - description: "Event - Just Above Range", - givenEvent: nativeRequests.EventTracker{ - Event: 5, // Range is currently 1-4 - Methods: []native1.EventTrackingMethod{1}, - }, - expectedError: "request.imp[4].native.request.eventtrackers[8].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", - }, - { - description: "Methods - Many Valid", - givenEvent: nativeRequests.EventTracker{ - Event: 1, - Methods: []native1.EventTrackingMethod{1, 2}, - }, - expectedError: "", - }, - { - description: "Methods - Empty", - givenEvent: nativeRequests.EventTracker{ - Event: 1, - Methods: []native1.EventTrackingMethod{}, - }, - expectedError: "request.imp[4].native.request.eventtrackers[8].method is required. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", - }, - { - description: "Methods - Exchange Specific - Boundary", - givenEvent: nativeRequests.EventTracker{ - Event: 1, - Methods: []native1.EventTrackingMethod{500}, - }, - expectedError: "", - }, - { - description: "Methods - Exchange Specific - Boundary + 1", - givenEvent: nativeRequests.EventTracker{ - Event: 1, - Methods: []native1.EventTrackingMethod{501}, - }, - expectedError: "", - }, - { - description: "Methods - Negative", - givenEvent: nativeRequests.EventTracker{ - Event: 1, - Methods: []native1.EventTrackingMethod{-1}, - }, - expectedError: "request.imp[4].native.request.eventtrackers[8].methods[0] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", - }, - { - description: "Methods - Just Above Range", - givenEvent: nativeRequests.EventTracker{ - Event: 1, - Methods: []native1.EventTrackingMethod{3}, // Known values are currently 1-2 - }, - expectedError: "request.imp[4].native.request.eventtrackers[8].methods[0] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", - }, - { - description: "Methods - Mixed Valid + Invalid", - givenEvent: nativeRequests.EventTracker{ - Event: 1, - Methods: []native1.EventTrackingMethod{1, -1}, - }, - expectedError: "request.imp[4].native.request.eventtrackers[8].methods[1] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", - }, - } - - for _, test := range testCases { - err := validateNativeEventTracker(test.givenEvent, impIndex, eventIndex) - if test.expectedError == "" { - assert.NoError(t, err, test.description) - } else { - assert.EqualError(t, err, test.expectedError, test.description) - } - } -} - -func TestValidateNativeAssetData(t *testing.T) { - impIndex := 4 - assetIndex := 8 - - testCases := []struct { - description string - givenData nativeRequests.Data - expectedError string - }{ - { - description: "Valid", - givenData: nativeRequests.Data{Type: 1}, - expectedError: "", - }, - { - description: "Exchange Specific - Boundary", - givenData: nativeRequests.Data{Type: 500}, - expectedError: "", - }, - { - description: "Exchange Specific - Boundary + 1", - givenData: nativeRequests.Data{Type: 501}, - expectedError: "", - }, - { - description: "Not Specified", - givenData: nativeRequests.Data{}, - expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", - }, - { - description: "Negative", - givenData: nativeRequests.Data{Type: -1}, - expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", - }, - { - description: "Just Above Range", - givenData: nativeRequests.Data{Type: 13}, // Range is currently 1-12 - expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", - }, - } - - for _, test := range testCases { - err := validateNativeAssetData(&test.givenData, impIndex, assetIndex) - if test.expectedError == "" { - assert.NoError(t, err, test.description) - } else { - assert.EqualError(t, err, test.expectedError, test.description) - } - } -} - -func TestAuctionResponseHeaders(t *testing.T) { - testCases := []struct { - description string - requestBody string - expectedStatus int - expectedHeaders func(http.Header) - }{ - { - description: "Success Response", - requestBody: validRequest(t, "site.json"), - expectedStatus: 200, - expectedHeaders: func(h http.Header) { - h.Set("X-Prebid", "pbs-go/unknown") - h.Set("Content-Type", "application/json") - }, - }, - { - description: "Failure Response", - requestBody: "{}", - expectedStatus: 400, - expectedHeaders: func(h http.Header) { - h.Set("X-Prebid", "pbs-go/unknown") - }, + description: "Failure Response with Chrome BrowsingTopicsHeader", + httpRequest: func() *http.Request { + httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader("{}")) + httpReq.Header.Add(secBrowsingTopics, "sample-value") + return httpReq + }(), + expectedStatus: 400, + expectedHeaders: func(h http.Header) { + h.Set("X-Prebid", "pbs-go/unknown") + h.Set("Observe-Browsing-Topics", "?1") + }, }, } @@ -4750,7 +3939,7 @@ func TestAuctionResponseHeaders(t *testing.T) { endpoint, _ := NewEndpoint( fakeUUIDGenerator{}, exchange, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, @@ -4765,10 +3954,9 @@ func TestAuctionResponseHeaders(t *testing.T) { ) for _, test := range testCases { - httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.requestBody)) recorder := httptest.NewRecorder() - endpoint(recorder, httpReq, nil) + endpoint(recorder, test.httpRequest, nil) expectedHeaders := http.Header{} test.expectedHeaders(expectedHeaders) @@ -4782,38 +3970,6 @@ func TestAuctionResponseHeaders(t *testing.T) { // Test stored request data -func TestValidateBanner(t *testing.T) { - impIndex := 0 - - testCases := []struct { - description string - banner *openrtb2.Banner - impIndex int - isInterstitial bool - expectedError error - }{ - { - description: "isInterstitial Equals False (not set to 1)", - banner: &openrtb2.Banner{W: nil, H: nil, Format: nil}, - impIndex: impIndex, - isInterstitial: false, - expectedError: errors.New("request.imp[0].banner has no sizes. Define \"w\" and \"h\", or include \"format\" elements."), - }, - { - description: "isInterstitial Equals True (is set to 1)", - banner: &openrtb2.Banner{W: nil, H: nil, Format: nil}, - impIndex: impIndex, - isInterstitial: true, - expectedError: nil, - }, - } - - for _, test := range testCases { - result := validateBanner(test.banner, test.impIndex, test.isInterstitial) - assert.Equal(t, test.expectedError, result, test.description) - } -} - func TestParseRequestMergeBidderParams(t *testing.T) { tests := []struct { name string @@ -4821,6 +3977,7 @@ func TestParseRequestMergeBidderParams(t *testing.T) { expectedImpExt json.RawMessage expectedReqExt json.RawMessage expectedErrorCount int + expectedErrors []error }{ { name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder", @@ -4838,10 +3995,16 @@ func TestParseRequestMergeBidderParams(t *testing.T) { }, { name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext for backward compatibility", - givenRequestBody: validRequest(t, "req-ext-bidder-params-backward-compatible-merge.json"), - expectedImpExt: getObject(t, "req-ext-bidder-params-backward-compatible-merge.json", "expectedImpExt"), - expectedReqExt: getObject(t, "req-ext-bidder-params-backward-compatible-merge.json", "expectedReqExt"), - expectedErrorCount: 0, + givenRequestBody: validRequest(t, "req-ext-bidder-params-promotion.json"), + expectedImpExt: getObject(t, "req-ext-bidder-params-promotion.json", "expectedImpExt"), + expectedReqExt: getObject(t, "req-ext-bidder-params-promotion.json", "expectedReqExt"), + expectedErrorCount: 1, + expectedErrors: []error{ + &errortypes.Warning{ + WarningCode: 0, + Message: "request.imp[0].ext contains unknown bidder: 'arbitraryObject', ignoring", + }, + }, }, } for _, test := range tests { @@ -4850,7 +4013,7 @@ func TestParseRequestMergeBidderParams(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, &warningsCheckExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -4898,6 +4061,8 @@ func TestParseRequestMergeBidderParams(t *testing.T) { assert.Equal(t, eReqE, reqE, "req.Ext should match") assert.Len(t, errL, test.expectedErrorCount, "error length should match") + + assert.Equal(t, errL, test.expectedErrors) }) } } @@ -4954,7 +4119,7 @@ func TestParseRequestStoredResponses(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, &warningsCheckExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -4991,8 +4156,8 @@ func TestParseRequestStoredResponses(t *testing.T) { } func TestParseRequestStoredBidResponses(t *testing.T) { - bidRespId1 := json.RawMessage(`{"id": "resp_id1", "seatbid": [{"bid": [{"id": "bid_id1"}], "seat": "testBidder1"}], "bidid": "123", "cur": "USD"}`) - bidRespId2 := json.RawMessage(`{"id": "resp_id2", "seatbid": [{"bid": [{"id": "bid_id2"}], "seat": "testBidder2"}], "bidid": "124", "cur": "USD"}`) + bidRespId1 := json.RawMessage(`{"id": "resp_id1", "seatbid": [{"bid": [{"id": "bid_id1"}], "seat": "telaria"}], "bidid": "123", "cur": "USD"}`) + bidRespId2 := json.RawMessage(`{"id": "resp_id2", "seatbid": [{"bid": [{"id": "bid_id2"}], "seat": "amx"}], "bidid": "124", "cur": "USD"}`) bidRespId3 := json.RawMessage(`{"id": "resp_id3", "seatbid": [{"bid": [{"id": "bid_id3"}], "seat": "APPNEXUS"}], "bidid": "125", "cur": "USD"}`) mockStoredBidResponses := map[string]json.RawMessage{ "bidResponseId1": bidRespId1, @@ -5011,15 +4176,23 @@ func TestParseRequestStoredBidResponses(t *testing.T) { name: "req imp has valid stored bid response", givenRequestBody: validRequest(t, "imp-with-stored-bid-resp.json"), expectedStoredBidResponses: map[string]map[string]json.RawMessage{ - "imp-id1": {"testBidder1": bidRespId1}, + "imp-id1": {"telaria": bidRespId1}, }, expectedErrorCount: 0, }, { - name: "req imp has valid stored bid response with case insensitive bidder name", - givenRequestBody: validRequest(t, "imp-with-stored-bid-resp-insensitive-bidder-name.json"), + name: "req imp has valid stored bid response with case not-matching bidder name", + givenRequestBody: validRequest(t, "imp-with-stored-bid-resp-case-not-matching-bidder-name.json"), expectedStoredBidResponses: map[string]map[string]json.RawMessage{ - "imp-id3": {"APPNEXUS": bidRespId3}, + "imp-id3": {"appnexus": bidRespId3}, + }, + expectedErrorCount: 0, + }, + { + name: "req imp has valid stored bid response with case matching bidder name", + givenRequestBody: validRequest(t, "imp-with-stored-bid-resp-case-matching-bidder-name.json"), + expectedStoredBidResponses: map[string]map[string]json.RawMessage{ + "imp-id3": {"appnexus": bidRespId3}, }, expectedErrorCount: 0, }, @@ -5027,8 +4200,8 @@ func TestParseRequestStoredBidResponses(t *testing.T) { name: "req has two imps with valid stored bid responses", givenRequestBody: validRequest(t, "req-two-imps-stored-bid-responses.json"), expectedStoredBidResponses: map[string]map[string]json.RawMessage{ - "imp-id1": {"testBidder1": bidRespId1}, - "imp-id2": {"testBidder2": bidRespId2}, + "imp-id1": {"telaria": bidRespId1}, + "imp-id2": {"amx": bidRespId2}, }, expectedErrorCount: 0, }, @@ -5036,7 +4209,7 @@ func TestParseRequestStoredBidResponses(t *testing.T) { name: "req has two imps one with valid stored bid responses and another one without stored bid responses", givenRequestBody: validRequest(t, "req-two-imps-with-and-without-stored-bid-responses.json"), expectedStoredBidResponses: map[string]map[string]json.RawMessage{ - "imp-id2": {"testBidder2": bidRespId2}, + "imp-id2": {"amx": bidRespId2}, }, expectedErrorCount: 0, }, @@ -5044,7 +4217,13 @@ func TestParseRequestStoredBidResponses(t *testing.T) { name: "req has two imps with missing stored bid responses", givenRequestBody: validRequest(t, "req-two-imps-missing-stored-bid-response.json"), expectedStoredBidResponses: nil, - expectedErrorCount: 2, + expectedErrorCount: 1, + }, + { + name: "req imp has valid stored bid response with non existing bidder name", + givenRequestBody: validRequest(t, "imp-with-stored-bid-resp-non-existing-bidder-name.json"), + expectedStoredBidResponses: nil, + expectedErrorCount: 1, }, } for _, test := range tests { @@ -5053,7 +4232,7 @@ func TestParseRequestStoredBidResponses(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, &warningsCheckExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -5063,7 +4242,7 @@ func TestParseRequestStoredBidResponses(t *testing.T) { map[string]string{}, false, []byte{}, - map[string]openrtb_ext.BidderName{"testBidder1": "testBidder1", "testBidder2": "testBidder2", "appnexus": "appnexus"}, + map[string]openrtb_ext.BidderName{"telaria": "telaria", "amx": "amx", "appnexus": "appnexus"}, nil, nil, hardcodedResponseIPValidator{response: true}, @@ -5078,6 +4257,7 @@ func TestParseRequestStoredBidResponses(t *testing.T) { req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody)) _, _, _, storedBidResponses, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor) if test.expectedErrorCount == 0 { + assert.Empty(t, errL) assert.Equal(t, test.expectedStoredBidResponses, storedBidResponses, "stored responses should match") } else { assert.Contains(t, errL[0].Error(), test.expectedError, "error should match") @@ -5090,7 +4270,7 @@ func TestValidateStoredResp(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, &nobidExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -5113,6 +4293,8 @@ func TestValidateStoredResp(t *testing.T) { testCases := []struct { description string givenRequestWrapper *openrtb_ext.RequestWrapper + givenHttpRequest *http.Request + givenAccount *config.Account expectedErrorList []error hasStoredAuctionResponses bool storedBidResponses stored_responses.ImpBidderStoredResp @@ -5485,7 +4667,7 @@ func TestValidateStoredResp(t *testing.T) { storedBidResponses: stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "rubicon": json.RawMessage(`{"test":true}`)}}, }, { - description: "One imp with 2 stored bid responses and 1 bidders in imp.ext and 1 in imp.ext.prebid.bidder, expect validate request to throw no errors", + description: "One imp with 1 stored bid response and 1 ignored bidder in imp.ext and 1 included bidder in imp.ext.prebid.bidder, expect validate request to throw no errors", givenRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{ ID: "Some-ID", @@ -5512,7 +4694,7 @@ func TestValidateStoredResp(t *testing.T) { }, expectedErrorList: []error{}, hasStoredAuctionResponses: false, - storedBidResponses: stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)}}, + storedBidResponses: stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"telaria": json.RawMessage(`{"test":true}`)}}, }, { description: "One imp with 2 stored bid responses and 1 bidders in imp.ext and 1 in imp.ext.prebid.bidder that is not defined in stored bid responses, expect validate request to throw an error", @@ -5653,8 +4835,10 @@ func TestValidateStoredResp(t *testing.T) { } for _, test := range testCases { - errorList := deps.validateRequest(test.givenRequestWrapper, false, test.hasStoredAuctionResponses, test.storedBidResponses, false) - assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description) + t.Run(test.description, func(t *testing.T) { + errorList := deps.validateRequest(test.givenAccount, test.givenHttpRequest, test.givenRequestWrapper, false, test.hasStoredAuctionResponses, test.storedBidResponses, false) + assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description) + }) } } @@ -5904,7 +5088,7 @@ func TestParseRequestMultiBid(t *testing.T) { deps := &endpointDeps{ fakeUUIDGenerator{}, &warningsCheckExchange{}, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -6091,3 +5275,726 @@ func TestValidateAliases(t *testing.T) { func fakeNormalizeBidderName(name string) (openrtb_ext.BidderName, bool) { return openrtb_ext.BidderName(strings.ToLower(name)), true } + +func TestValidateOrFillCookieDeprecation(t *testing.T) { + type args struct { + httpReq *http.Request + req *openrtb_ext.RequestWrapper + account config.Account + } + tests := []struct { + name string + args args + wantDeviceExt json.RawMessage + wantErr error + }{ + { + name: "account-nil", + wantDeviceExt: nil, + wantErr: nil, + }, + { + name: "cookie-deprecation-not-enabled", + args: args{ + httpReq: &http.Request{ + Header: http.Header{secCookieDeprecation: []string{"example_label_1"}}, + }, + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + }, + wantDeviceExt: nil, + wantErr: nil, + }, + { + name: "cookie-deprecation-disabled-explicitly", + args: args{ + httpReq: &http.Request{ + Header: http.Header{secCookieDeprecation: []string{"example_label_1"}}, + }, + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + account: config.Account{ + Privacy: config.AccountPrivacy{ + PrivacySandbox: config.PrivacySandbox{ + CookieDeprecation: config.CookieDeprecation{ + Enabled: false, + }, + }, + }, + }, + }, + wantDeviceExt: nil, + wantErr: nil, + }, + { + name: "cookie-deprecation-enabled-header-not-present-in-request", + args: args{ + httpReq: &http.Request{}, + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + account: config.Account{ + Privacy: config.AccountPrivacy{ + PrivacySandbox: config.PrivacySandbox{ + CookieDeprecation: config.CookieDeprecation{ + Enabled: true, + }, + }, + }, + }, + }, + wantDeviceExt: nil, + wantErr: nil, + }, + { + name: "header-present-request-device-nil", + args: args{ + httpReq: &http.Request{ + Header: http.Header{secCookieDeprecation: []string{"example_label_1"}}, + }, + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + account: config.Account{ + Privacy: config.AccountPrivacy{ + PrivacySandbox: config.PrivacySandbox{ + CookieDeprecation: config.CookieDeprecation{ + Enabled: true, + }, + }, + }, + }, + }, + wantDeviceExt: json.RawMessage(`{"cdep":"example_label_1"}`), + wantErr: nil, + }, + { + name: "header-present-request-device-ext-nil", + args: args{ + httpReq: &http.Request{ + Header: http.Header{secCookieDeprecation: []string{"example_label_1"}}, + }, + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + Ext: nil, + }, + }, + }, + account: config.Account{ + Privacy: config.AccountPrivacy{ + PrivacySandbox: config.PrivacySandbox{ + CookieDeprecation: config.CookieDeprecation{ + Enabled: true, + }, + }, + }, + }, + }, + wantDeviceExt: json.RawMessage(`{"cdep":"example_label_1"}`), + wantErr: nil, + }, + { + name: "header-present-request-device-ext-not-nil", + args: args{ + httpReq: &http.Request{ + Header: http.Header{secCookieDeprecation: []string{"example_label_1"}}, + }, + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + Ext: json.RawMessage(`{"foo":"bar"}`), + }, + }, + }, + account: config.Account{ + Privacy: config.AccountPrivacy{ + PrivacySandbox: config.PrivacySandbox{ + CookieDeprecation: config.CookieDeprecation{ + Enabled: true, + }, + }, + }, + }, + }, + wantDeviceExt: json.RawMessage(`{"cdep":"example_label_1","foo":"bar"}`), + wantErr: nil, + }, + { + name: "header-present-with-length-more-than-100", + args: args{ + httpReq: &http.Request{ + Header: http.Header{secCookieDeprecation: []string{"zjfXqGxXFI8yura8AhQl1DK2EMMmryrC8haEpAlwjoerrFfEo2MQTXUq6cSmLohI8gjsnkGU4oAzvXd4TTAESzEKsoYjRJ2zKxmEa"}}, + }, + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + Ext: json.RawMessage(`{"foo":"bar"}`), + }, + }, + }, + account: config.Account{ + Privacy: config.AccountPrivacy{ + PrivacySandbox: config.PrivacySandbox{ + CookieDeprecation: config.CookieDeprecation{ + Enabled: true, + }, + }, + }, + }, + }, + wantDeviceExt: json.RawMessage(`{"foo":"bar"}`), + wantErr: &errortypes.Warning{ + Message: "request.device.ext.cdep must not exceed 100 characters", + WarningCode: errortypes.SecCookieDeprecationLenWarningCode, + }, + }, + { + name: "header-present-request-device-ext-cdep-present", + args: args{ + httpReq: &http.Request{ + Header: http.Header{secCookieDeprecation: []string{"example_label_1"}}, + }, + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + Ext: json.RawMessage(`{"foo":"bar","cdep":"example_label_2"}`), + }, + }, + }, + account: config.Account{ + Privacy: config.AccountPrivacy{ + PrivacySandbox: config.PrivacySandbox{ + CookieDeprecation: config.CookieDeprecation{ + Enabled: true, + }, + }, + }, + }, + }, + wantDeviceExt: json.RawMessage(`{"foo":"bar","cdep":"example_label_2"}`), + wantErr: nil, + }, + { + name: "header-present-request-device-ext-invalid", + args: args{ + httpReq: &http.Request{ + Header: http.Header{secCookieDeprecation: []string{"example_label_1"}}, + }, + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + Ext: json.RawMessage(`{`), + }, + }, + }, + account: config.Account{ + Privacy: config.AccountPrivacy{ + PrivacySandbox: config.PrivacySandbox{ + CookieDeprecation: config.CookieDeprecation{ + Enabled: true, + }, + }, + }, + }, + }, + wantDeviceExt: json.RawMessage(`{`), + wantErr: &errortypes.FailedToUnmarshal{ + Message: "expects \" or n, but found \x00", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateOrFillCookieDeprecation(tt.args.httpReq, tt.args.req, &tt.args.account) + assert.Equal(t, tt.wantErr, err) + if tt.args.req != nil { + err := tt.args.req.RebuildRequest() + assert.NoError(t, err) + } + if tt.wantDeviceExt == nil { + if tt.args.req != nil && tt.args.req.Device != nil { + assert.Nil(t, tt.args.req.Device.Ext) + } + } else { + assert.Equal(t, string(tt.wantDeviceExt), string(tt.args.req.Device.Ext)) + } + }) + } +} + +func TestSetGPCImplicitly(t *testing.T) { + testCases := []struct { + description string + header string + regs *openrtb2.Regs + expectError bool + expectedRegs *openrtb2.Regs + }{ + { + description: "regs_ext_gpc_not_set_and_header_is_1", + header: "1", + regs: &openrtb2.Regs{ + Ext: []byte(`{}`), + }, + expectError: false, + expectedRegs: &openrtb2.Regs{ + Ext: []byte(`{"gpc":"1"}`), + }, + }, + { + description: "sec_gpc_header_not_set_gpc_should_not_be_modified", + header: "", + regs: &openrtb2.Regs{ + Ext: []byte(`{}`), + }, + expectError: false, + expectedRegs: &openrtb2.Regs{ + Ext: []byte(`{}`), + }, + }, + { + description: "sec_gpc_header_set_to_2_gpc_should_not_be_modified", + header: "2", + regs: &openrtb2.Regs{ + Ext: []byte(`{}`), + }, + expectError: false, + expectedRegs: &openrtb2.Regs{ + Ext: []byte(`{}`), + }, + }, + { + description: "sec_gpc_header_set_to_1_and_regs_ext_contains_other_data", + header: "1", + regs: &openrtb2.Regs{ + Ext: []byte(`{"some_other_field":"some_value"}`), + }, + expectError: false, + expectedRegs: &openrtb2.Regs{ + Ext: []byte(`{"some_other_field":"some_value","gpc":"1"}`), + }, + }, + { + description: "regs_ext_gpc_not_set_and_header_not_set", + header: "", + regs: &openrtb2.Regs{ + Ext: []byte(`{}`), + }, + expectError: false, + expectedRegs: &openrtb2.Regs{ + Ext: []byte(`{}`), + }, + }, + { + description: "regs_ext_gpc_not_set_and_header_not_1", + header: "0", + regs: &openrtb2.Regs{ + Ext: []byte(`{}`), + }, + expectError: false, + expectedRegs: &openrtb2.Regs{ + Ext: []byte(`{}`), + }, + }, + { + description: "regs_ext_gpc_is_1_and_header_is_1", + header: "1", + regs: &openrtb2.Regs{ + Ext: []byte(`{"gpc":"1"}`), + }, + expectError: false, + expectedRegs: &openrtb2.Regs{ + Ext: []byte(`{"gpc":"1"}`), + }, + }, + { + description: "regs_ext_gpc_is_1_and_header_not_1", + header: "0", + regs: &openrtb2.Regs{ + Ext: []byte(`{"gpc":"1"}`), + }, + expectError: false, + expectedRegs: &openrtb2.Regs{ + Ext: []byte(`{"gpc":"1"}`), + }, + }, + { + description: "regs_ext_other_data_and_header_is_1", + header: "1", + regs: &openrtb2.Regs{ + Ext: []byte(`{"other":"value"}`), + }, + expectError: false, + expectedRegs: &openrtb2.Regs{ + Ext: []byte(`{"other":"value","gpc":"1"}`), + }, + }, + { + description: "regs_nil_and_header_is_1", + header: "1", + regs: nil, + expectError: false, + expectedRegs: &openrtb2.Regs{ + Ext: []byte(`{"gpc":"1"}`), + }, + }, + { + description: "regs_nil_and_header_not_set", + header: "", + regs: nil, + expectError: false, + expectedRegs: nil, + }, + { + description: "regs_ext_is_nil_and_header_not_set", + header: "", + regs: &openrtb2.Regs{ + Ext: nil, + }, + expectError: false, + expectedRegs: &openrtb2.Regs{ + Ext: nil, + }, + }, + } + + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + httpReq := &http.Request{ + Header: http.Header{ + http.CanonicalHeaderKey("Sec-GPC"): []string{test.header}, + }, + } + + r := &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: test.regs, + }, + } + + err := setGPCImplicitly(httpReq, r) + + if test.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + assert.NoError(t, r.RebuildRequest()) + if test.expectedRegs == nil { + assert.Nil(t, r.BidRequest.Regs) + } else if test.expectedRegs.Ext == nil { + assert.Nil(t, r.BidRequest.Regs.Ext) + } else { + assert.JSONEq(t, string(test.expectedRegs.Ext), string(r.BidRequest.Regs.Ext)) + } + }) + } +} + +func TestValidateRequestCookieDeprecation(t *testing.T) { + testCases := + []struct { + name string + givenAccount *config.Account + httpReq *http.Request + reqWrapper *openrtb_ext.RequestWrapper + wantErrs []error + wantCDep string + }{ + { + name: "header-with-length-less-than-100", + httpReq: func() *http.Request { + req := httptest.NewRequest("POST", "/openrtb2/auction", nil) + req.Header.Set(secCookieDeprecation, "sample-value") + return req + }(), + givenAccount: &config.Account{ + ID: "1", + Privacy: config.AccountPrivacy{ + PrivacySandbox: config.PrivacySandbox{ + CookieDeprecation: config.CookieDeprecation{ + Enabled: true, + TTLSec: 86400, + }, + }, + }, + }, + reqWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "Some-ID", + App: &openrtb2.App{}, + Imp: []openrtb2.Imp{ + { + ID: "Some-Imp-ID", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 600, + H: 500, + }, + { + W: 300, + H: 600, + }, + }, + }, + Ext: []byte(`{"pubmatic":{"publisherId": 12345678}}`), + }, + }, + }, + }, + wantErrs: []error{}, + wantCDep: "sample-value", + }, + { + name: "header-with-length-more-than-100", + httpReq: func() *http.Request { + req := httptest.NewRequest("POST", "/openrtb2/auction", nil) + req.Header.Set(secCookieDeprecation, "zjfXqGxXFI8yura8AhQl1DK2EMMmryrC8haEpAlwjoerrFfEo2MQTXUq6cSmLohI8gjsnkGU4oAzvXd4TTAESzEKsoYjRJ2zKxmEa") + return req + }(), + givenAccount: &config.Account{ + ID: "1", + Privacy: config.AccountPrivacy{ + PrivacySandbox: config.PrivacySandbox{ + CookieDeprecation: config.CookieDeprecation{ + Enabled: true, + TTLSec: 86400, + }, + }, + }, + }, + reqWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "Some-ID", + App: &openrtb2.App{}, + Imp: []openrtb2.Imp{ + { + ID: "Some-Imp-ID", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 600, + H: 500, + }, + { + W: 300, + H: 600, + }, + }, + }, + Ext: []byte(`{"pubmatic":{"publisherId": 12345678}}`), + }, + }, + }, + }, + wantErrs: []error{ + &errortypes.Warning{ + Message: "request.device.ext.cdep must not exceed 100 characters", + WarningCode: errortypes.SecCookieDeprecationLenWarningCode, + }, + }, + wantCDep: "", + }, + } + + deps := &endpointDeps{ + fakeUUIDGenerator{}, + &warningsCheckExchange{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + &mockAccountFetcher{}, + &config.Configuration{}, + &metricsConfig.NilMetricsEngine{}, + analyticsBuild.New(&config.Analytics{}), + map[string]string{}, + false, + []byte{}, + openrtb_ext.BuildBidderMap(), + nil, + nil, + hardcodedResponseIPValidator{response: true}, + empty_fetcher.EmptyFetcher{}, + hooks.EmptyPlanBuilder{}, + nil, + openrtb_ext.NormalizeBidderName, + } + + for _, test := range testCases { + errs := deps.validateRequest(test.givenAccount, test.httpReq, test.reqWrapper, false, false, stored_responses.ImpBidderStoredResp{}, false) + assert.Equal(t, test.wantErrs, errs) + test.reqWrapper.RebuildRequest() + deviceExt, err := test.reqWrapper.GetDeviceExt() + assert.NoError(t, err) + assert.Equal(t, test.wantCDep, deviceExt.GetCDep()) + } +} + +func TestSetSecBrowsingTopicsImplicitly(t *testing.T) { + type args struct { + httpReq *http.Request + r *openrtb_ext.RequestWrapper + account *config.Account + } + tests := []struct { + name string + args args + wantUser *openrtb2.User + }{ + { + name: "empty HTTP request, no change in user data", + args: args{ + httpReq: &http.Request{}, + r: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}}, + account: &config.Account{Privacy: config.AccountPrivacy{PrivacySandbox: config.PrivacySandbox{TopicsDomain: "ads.pubmatic.com"}}}, + }, + wantUser: nil, + }, + { + name: "valid topic in request but topicsdomain not configured by host, no change in user data", + args: args{ + httpReq: &http.Request{ + Header: http.Header{ + secBrowsingTopics: []string{"(1);v=chrome.1:1:2, ();p=P00000000000"}, + }, + }, + r: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}}, + account: &config.Account{Privacy: config.AccountPrivacy{PrivacySandbox: config.PrivacySandbox{TopicsDomain: ""}}}, + }, + wantUser: nil, + }, + { + name: "valid topic in request and topicsdomain configured by host, topics copied to user data", + args: args{ + httpReq: &http.Request{ + Header: http.Header{ + secBrowsingTopics: []string{"(1);v=chrome.1:1:2, ();p=P00000000000"}, + }, + }, + r: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}}, + account: &config.Account{Privacy: config.AccountPrivacy{PrivacySandbox: config.PrivacySandbox{TopicsDomain: "ads.pubmatic.com"}}}, + }, + wantUser: &openrtb2.User{ + Data: []openrtb2.Data{ + { + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + { + ID: "1", + }, + }, + Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`), + }, + }, + }, + }, + { + name: "valid empty topic in request, no change in user data", + args: args{ + httpReq: &http.Request{ + Header: http.Header{ + secBrowsingTopics: []string{"();p=P0000000000000000000000000000000"}, + }, + }, + r: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}}, + account: &config.Account{Privacy: config.AccountPrivacy{PrivacySandbox: config.PrivacySandbox{TopicsDomain: "ads.pubmatic.com"}}}, + }, + wantUser: nil, + }, + { + name: "request with a few valid topics (including duplicate topics, segIDs, matching segtax, segclass, etc) and a few invalid topics(different invalid format), only valid and unique topics copied/merged to/with user data", + args: args{ + httpReq: &http.Request{ + Header: http.Header{ + secBrowsingTopics: []string{"(1);v=chrome.1:1:2, (1 2);v=chrome.1:1:2,(4);v=chrome.1:1:2,();p=P0000000000,(4);v=chrome.1, 5);v=chrome.1, (6;v=chrome.1, ();v=chrome.1, ( );v=chrome.1, (1);v=chrome.1:1:2, (1 2 4 6 7 4567 ) ; v=chrome.1: 2 : 3,();p=P0000000000"}, + }, + }, + r: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ + Data: []openrtb2.Data{ + { + Name: "chrome.com", + Segment: []openrtb2.Segment{ + {ID: "1"}, + }, + Ext: json.RawMessage(`{"segtax":603,"segclass":"4"}`), + }, + { + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "3"}, + }, + Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`), + }, + }, + }, + }}, + account: &config.Account{Privacy: config.AccountPrivacy{PrivacySandbox: config.PrivacySandbox{TopicsDomain: "ads.pubmatic.com"}}}, + }, + wantUser: &openrtb2.User{ + Data: []openrtb2.Data{ + { + Name: "chrome.com", + Segment: []openrtb2.Segment{ + {ID: "1"}, + }, + Ext: json.RawMessage(`{"segtax":603,"segclass":"4"}`), + }, + { + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + {ID: "3"}, + {ID: "4"}, + }, + Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`), + }, + { + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + {ID: "4"}, + {ID: "6"}, + {ID: "7"}, + {ID: "4567"}, + }, + Ext: json.RawMessage(`{"segtax":601,"segclass":"3"}`), + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + setSecBrowsingTopicsImplicitly(tt.args.httpReq, tt.args.r, tt.args.account) + + // sequence is not guaranteed we're using a map to filter segids + sortUserData(tt.wantUser) + sortUserData(tt.args.r.User) + assert.Equal(t, tt.wantUser, tt.args.r.User, tt.name) + }) + } +} + +func sortUserData(user *openrtb2.User) { + if user != nil { + sort.Slice(user.Data, func(i, j int) bool { + if user.Data[i].Name == user.Data[j].Name { + return string(user.Data[i].Ext) < string(user.Data[j].Ext) + } + return user.Data[i].Name < user.Data[j].Name + }) + for g := range user.Data { + sort.Slice(user.Data[g].Segment, func(i, j int) bool { + return user.Data[g].Segment[i].ID < user.Data[g].Segment[j].ID + }) + } + } +} diff --git a/endpoints/openrtb2/interstitial.go b/endpoints/openrtb2/interstitial.go index 46dc7a61510..4cb9482c0d5 100644 --- a/endpoints/openrtb2/interstitial.go +++ b/endpoints/openrtb2/interstitial.go @@ -3,10 +3,10 @@ package openrtb2 import ( "fmt" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func processInterstitials(req *openrtb_ext.RequestWrapper) error { diff --git a/endpoints/openrtb2/interstitial_test.go b/endpoints/openrtb2/interstitial_test.go index 947817803b2..d5276d18dba 100644 --- a/endpoints/openrtb2/interstitial_test.go +++ b/endpoints/openrtb2/interstitial_test.go @@ -4,8 +4,8 @@ import ( "encoding/json" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/openrtb2/sample-requests/aliased/simple.json b/endpoints/openrtb2/sample-requests/aliased/default-request-alias.json similarity index 76% rename from endpoints/openrtb2/sample-requests/aliased/simple.json rename to endpoints/openrtb2/sample-requests/aliased/default-request-alias.json index 6e1440a7c50..134d121b417 100644 --- a/endpoints/openrtb2/sample-requests/aliased/simple.json +++ b/endpoints/openrtb2/sample-requests/aliased/default-request-alias.json @@ -35,7 +35,17 @@ { "id": "appnexus-bid", "impid": "my-imp-id", - "price": 0 + "price": 0, + "ext": { + "origbidcpm": 0, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } } ], "seat": "alias1" diff --git a/endpoints/openrtb2/sample-requests/aliased/hard-alias.json b/endpoints/openrtb2/sample-requests/aliased/hard-alias.json new file mode 100644 index 00000000000..7e7278148f4 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/aliased/hard-alias.json @@ -0,0 +1,62 @@ +{ + "description": "Imp extension references valid bidder alias of adf defined in the adform bidder infos config yaml file.", + "config": { + "mockBidders": [ + { + "bidderName": "adform", + "currency": "USD", + "price": 2 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "adform": { + "mid": "123" + } + } + } + ] + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "USD", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "adform-bid", + "impid": "my-imp-id", + "price": 2, + "ext": { + "origbidcpm": 2, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "adform" + }, + "type": "banner" + } + } + } + ], + "seat": "adform" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/aliased/request-alias.json b/endpoints/openrtb2/sample-requests/aliased/request-alias.json new file mode 100644 index 00000000000..77ce02adbf6 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/aliased/request-alias.json @@ -0,0 +1,61 @@ +{ + "description": "Imp extension references valid bidder alias of appnexus defined in the request ext.prebid.aliases field.", + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "alias1": { + "placementId": 12883451 + } + } + } + ], + "ext": { + "prebid": { + "aliases": { + "alias1": "appnexus" + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "USD", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "my-imp-id", + "price": 0, + "ext": { + "origbidcpm": 0, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } + } + ], + "seat": "alias1" + } + ] + }, + "expectedReturnCode": 200 + } + \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/alternate-bidder-code/alternate-bidder-code.json b/endpoints/openrtb2/sample-requests/alternate-bidder-code/alternate-bidder-code.json new file mode 100644 index 00000000000..75e951cbe49 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/alternate-bidder-code/alternate-bidder-code.json @@ -0,0 +1,67 @@ +{ + "description": "Imp extension doesn't come with valid bidder name but does come with valid bidder alias as defined in the mockAliases list.", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 2, + "seat": "groupm" + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com", + "publisher": { + "id": "alternate_bidder_code_acct" + } + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "USD", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "my-imp-id", + "price": 2, + "ext": { + "origbidcpm": 2, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } + } + ], + "seat": "groupm" + } + ] + }, + "expectedReturnCode": 200 + } + \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/amp/consent-through-query/addtl-consent-through-query.json b/endpoints/openrtb2/sample-requests/amp/consent-through-query/addtl-consent-through-query.json index 034d3235e15..c775d332ecd 100644 --- a/endpoints/openrtb2/sample-requests/amp/consent-through-query/addtl-consent-through-query.json +++ b/endpoints/openrtb2/sample-requests/amp/consent-through-query/addtl-consent-through-query.json @@ -105,8 +105,7 @@ ] }, "includewinners": true, - "includebidderkeys": true, - "mediatypepricegranularity": {} + "includebidderkeys": true } } } diff --git a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-ccpa-through-query.json b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-ccpa-through-query.json index 8dc19f6f24d..ab9657d51f5 100644 --- a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-ccpa-through-query.json +++ b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-ccpa-through-query.json @@ -74,9 +74,7 @@ } ], "regs": { - "ext": { - "us_privacy": "1YYY" - } + "us_privacy": "1YYY" }, "ext": { "prebid": { @@ -99,8 +97,7 @@ ] }, "includewinners": true, - "includebidderkeys": true, - "mediatypepricegranularity": {} + "includebidderkeys": true } } } diff --git a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-legacy-tcf2-consent-through-query.json b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-legacy-tcf2-consent-through-query.json index 4003abf99cb..3d6c0bc0157 100644 --- a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-legacy-tcf2-consent-through-query.json +++ b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-legacy-tcf2-consent-through-query.json @@ -74,14 +74,10 @@ } ], "regs": { - "ext": { - "gdpr": 1 - } + "gdpr": 1 }, "user": { - "ext": { - "consent": "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA" - } + "consent": "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA" }, "ext": { "prebid": { @@ -104,8 +100,7 @@ ] }, "includewinners": true, - "includebidderkeys": true, - "mediatypepricegranularity": {} + "includebidderkeys": true } } } diff --git a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf1-consent-through-query.json b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf1-consent-through-query.json index c6389dadc29..c4f3da140a3 100644 --- a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf1-consent-through-query.json +++ b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf1-consent-through-query.json @@ -79,9 +79,7 @@ } ], "regs": { - "ext": { - "gdpr": 1 - } + "gdpr": 1 }, "ext": { "prebid": { @@ -104,8 +102,7 @@ ] }, "includewinners": true, - "includebidderkeys": true, - "mediatypepricegranularity": {} + "includebidderkeys": true } } } diff --git a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf2-consent-through-query.json b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf2-consent-through-query.json index b62a745b1bf..2be461cdf6c 100644 --- a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf2-consent-through-query.json +++ b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf2-consent-through-query.json @@ -74,14 +74,10 @@ } ], "regs": { - "ext": { - "gdpr": 1 - } + "gdpr": 1 }, "user": { - "ext": { - "consent": "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA" - } + "consent": "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA" }, "ext": { "prebid": { @@ -104,8 +100,7 @@ ] }, "includewinners": true, - "includebidderkeys": true, - "mediatypepricegranularity": {} + "includebidderkeys": true } } } diff --git a/endpoints/openrtb2/sample-requests/amp/valid-supplementary/ortb-2.5-to-2.6-upconvert.json b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/ortb-2.5-to-2.6-upconvert.json new file mode 100644 index 00000000000..1a2db473cdd --- /dev/null +++ b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/ortb-2.5-to-2.6-upconvert.json @@ -0,0 +1,305 @@ +{ + "description": "Amp request with all 2.5 ext fields that were moved into 2.6 ortb fields", + "query": "tag_id=101", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 15 + } + ] + }, + "mockBidRequest": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + }, + "is_rewarded_inventory": 1 + } + } + } + ], + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1YYY" + } + }, + "user": { + "ext": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + } + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + } + } + }, + "expectedValidatedBidRequest": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": { + "page": "prebid.org", + "ext": { + "amp": 1 + } + }, + "device": { + "ip": "192.0.2.1" + }, + "at": 1, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } + } + }, + "secure": 1, + "rwdd": 1 + } + ], + "regs": { + "gdpr": 1, + "us_privacy": "1YYY" + }, + "user": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + }, + "source": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + }, + "ext": { + "prebid": { + "cache": { + "bids": {} + }, + "channel": { + "name": "amp", + "version": "" + }, + "targeting": { + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "min": 0, + "max": 20, + "increment": 0.1 + } + ] + }, + "includewinners": true, + "includebidderkeys": true + } + } + } + }, + "expectedMockBidderRequests": { + "appnexus": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": { + "page": "prebid.org", + "ext": { + "amp": 1 + } + }, + "device": { + "ip": "192.0.2.1" + }, + "at": 1, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": 12883451 + }, + "prebid": { + "is_rewarded_inventory": 1 + } + }, + "secure": 1 + } + ], + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1YYY" + } + }, + "user": { + "ext": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + } + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + } + }, + "ext": { + "prebid": { + "channel": { + "name": "amp", + "version": "" + } + } + } + } + }, + "expectedAmpResponse": { + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_id": "0", + "hb_cache_id_appnexus": "0", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "15.00", + "hb_pb_appnexus": "15.00" + }, + "ortb2": { + "ext": { + "warnings": { + "general": [ + { + "code": 10002, + "message": "debug turned off for account" + } + ] + } + } + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/blacklisted/blacklisted-app.json b/endpoints/openrtb2/sample-requests/blocked/blocked-app.json similarity index 96% rename from endpoints/openrtb2/sample-requests/blacklisted/blacklisted-app.json rename to endpoints/openrtb2/sample-requests/blocked/blocked-app.json index 120fcec08f4..88e0ed43496 100644 --- a/endpoints/openrtb2/sample-requests/blacklisted/blacklisted-app.json +++ b/endpoints/openrtb2/sample-requests/blocked/blocked-app.json @@ -1,7 +1,7 @@ { - "description": "This is a perfectly valid request except that it comes from a blacklisted App", + "description": "This is a perfectly valid request except that it comes from a blocked app", "config": { - "blacklistedApps": ["spam_app"] + "blockedApps": ["spam_app"] }, "mockBidRequest": { "id": "some-request-id", diff --git a/endpoints/openrtb2/sample-requests/disabled/good/partial.json b/endpoints/openrtb2/sample-requests/disabled/good/partial.json index 01b6177fb26..0918b67bdc0 100644 --- a/endpoints/openrtb2/sample-requests/disabled/good/partial.json +++ b/endpoints/openrtb2/sample-requests/disabled/good/partial.json @@ -43,7 +43,7 @@ "openx": { "unit": "539439964", "delDomain": "se-demo-d.openx.net", - "customFloor": 0.1, + "customFloor": "0.5", "customParams": {"foo": "bar"} } } diff --git a/endpoints/openrtb2/sample-requests/hooks/auction_bidder_reject.json b/endpoints/openrtb2/sample-requests/hooks/auction_bidder_reject.json index 94769e4f6cd..0bc86e4c4a5 100644 --- a/endpoints/openrtb2/sample-requests/hooks/auction_bidder_reject.json +++ b/endpoints/openrtb2/sample-requests/hooks/auction_bidder_reject.json @@ -32,7 +32,7 @@ } } ], - "tmax": 50, + "tmax": 500, "test": 1, "ext": { "prebid": { diff --git a/endpoints/openrtb2/sample-requests/hooks/auction_bidder_response_reject.json b/endpoints/openrtb2/sample-requests/hooks/auction_bidder_response_reject.json index 6198e3e23bc..16a7f43c87a 100644 --- a/endpoints/openrtb2/sample-requests/hooks/auction_bidder_response_reject.json +++ b/endpoints/openrtb2/sample-requests/hooks/auction_bidder_response_reject.json @@ -32,7 +32,7 @@ } } ], - "tmax": 50, + "tmax": 500, "test": 1, "ext": { "prebid": { diff --git a/endpoints/openrtb2/sample-requests/hooks/auction_entrypoint_reject.json b/endpoints/openrtb2/sample-requests/hooks/auction_entrypoint_reject.json index c67b8d4b490..3481b986bc3 100644 --- a/endpoints/openrtb2/sample-requests/hooks/auction_entrypoint_reject.json +++ b/endpoints/openrtb2/sample-requests/hooks/auction_entrypoint_reject.json @@ -28,7 +28,7 @@ } } ], - "tmax": 50, + "tmax": 500, "ext": { "prebid": { "trace": "verbose" diff --git a/endpoints/openrtb2/sample-requests/hooks/auction_processed_auction_request_reject.json b/endpoints/openrtb2/sample-requests/hooks/auction_processed_auction_request_reject.json index eca136cf75f..4c160dec007 100644 --- a/endpoints/openrtb2/sample-requests/hooks/auction_processed_auction_request_reject.json +++ b/endpoints/openrtb2/sample-requests/hooks/auction_processed_auction_request_reject.json @@ -28,7 +28,7 @@ } } ], - "tmax": 50, + "tmax": 500, "ext": { "prebid": { "trace": "verbose" diff --git a/endpoints/openrtb2/sample-requests/hooks/auction_raw_auction_request_reject.json b/endpoints/openrtb2/sample-requests/hooks/auction_raw_auction_request_reject.json index dc192baffee..ed284e6687d 100644 --- a/endpoints/openrtb2/sample-requests/hooks/auction_raw_auction_request_reject.json +++ b/endpoints/openrtb2/sample-requests/hooks/auction_raw_auction_request_reject.json @@ -28,7 +28,7 @@ } } ], - "tmax": 50, + "tmax": 500, "ext": { "prebid": { "trace": "verbose" diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/unknown-bidder.json b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-prebid-bidder-empty.json similarity index 80% rename from endpoints/openrtb2/sample-requests/invalid-whole/unknown-bidder.json rename to endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-prebid-bidder-empty.json index a907d4d9257..d87a6d33261 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/unknown-bidder.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-prebid-bidder-empty.json @@ -21,6 +21,9 @@ ] }, "ext": { + "prebid": { + "bidder": {} + }, "appnexus": { "placementId": 12883451 }, @@ -34,5 +37,5 @@ "tmax": 500 }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request" + "expectedErrorMessage": "Invalid request: request.imp[0].ext.prebid.bidder must contain at least one bidder\n" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-unknown-bidder.json b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-unknown-bidder.json index 8a7b7932179..6b25ee8b037 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-unknown-bidder.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-unknown-bidder.json @@ -22,5 +22,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.imp[0].ext.prebid.bidder contains unknown bidder: noBidderShouldEverHaveThisName. Did you forget an alias in request.ext.prebid.aliases?\n" + "expectedErrorMessage": "Invalid request: request.imp[0].ext.prebid.bidder must contain at least one bidder" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/invalid-bidder-params.json b/endpoints/openrtb2/sample-requests/invalid-whole/invalid-bidder-params.json new file mode 100644 index 00000000000..0107ef9420e --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/invalid-bidder-params.json @@ -0,0 +1,38 @@ +{ + "description": "Required appnexus bidder param is provided but the type is invalid", + "config": { + "realParamsValidator": true + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": [] + } + } + } + ], + "tmax": 500, + "ext": {} + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].ext.prebid.bidder.appnexus failed validation.\nplacementId: Invalid type. Expected: [integer,string], given: array\n" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json deleted file mode 100644 index dc15410a290..00000000000 --- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "description": "Invalid GDPR value in regs field", - "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": { - "gdpr": "foo" - } - }, - "user": { - "ext": {} - } - }, - "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: gdpr must be an integer\n" -} diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json index 7ab2631b701..4a513f703b1 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json @@ -42,5 +42,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: expect { or n, but found " + "expectedErrorMessage": "Invalid request: req.regs.ext is invalid: expect { or n, but found " } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-gdpr-invalid.json similarity index 86% rename from endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json rename to endpoints/openrtb2/sample-requests/invalid-whole/regs-gdpr-invalid.json index 03e789eef86..40b7281d572 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/regs-gdpr-invalid.json @@ -35,14 +35,12 @@ } ], "regs": { - "ext": { - "gdpr": 2 - } + "gdpr": 2 }, "user": { "ext": {} } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.regs.ext.gdpr must be either 0 or 1\n" + "expectedErrorMessage": "Invalid request: request.regs.gdpr must be either 0 or 1\n" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-id-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-source-empty.json similarity index 63% rename from endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-id-empty.json rename to endpoints/openrtb2/sample-requests/invalid-whole/user-eids-source-empty.json index 910e9650d75..902a2d9c1b6 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-id-empty.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-source-empty.json @@ -1,5 +1,5 @@ { - "description": "Bid request where a request.user.ext.eids.uids array element is missing its id field", + "description": "Bid request with user.eids array element that does not contain source field", "mockBidRequest": { "id": "anyRequestID", "site": { @@ -29,14 +29,13 @@ }], "tmax": 1000, "user": { - "ext": { - "eids": [{ - "source": "source1", - "uids": [{}] + "eids": [{ + "uids": [{ + "id": "A" }] - } + }] } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext.eids[0].uids[0] missing required field: \"id\"\n" + "expectedErrorMessage": "Invalid request: request.user.eids[0] missing required field: \"source\"\n" } \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-id-empty.json similarity index 62% rename from endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-empty.json rename to endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-id-empty.json index 3a451ecbd76..c8eb07aa335 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-empty.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-id-empty.json @@ -1,5 +1,5 @@ { - "description": "Bid request with user.ext.eids array element array element that does not contain source field", + "description": "Bid request where a request.user.eids.uids array element is missing its id field", "mockBidRequest": { "id": "anyRequestID", "site": { @@ -29,15 +29,12 @@ }], "tmax": 1000, "user": { - "ext": { - "eids": [{ - "uids": [{ - "id": "A" - }] - }] - } + "eids": [{ + "source": "source1", + "uids": [{}] + }] } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext.eids[0] missing required field: \"source\"\n" + "expectedErrorMessage": "Invalid request: request.user.eids[0].uids[0] missing required field: \"id\"\n" } \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-missing.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-missing.json similarity index 70% rename from endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-missing.json rename to endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-missing.json index eed386b4c7d..3e55c33b849 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-missing.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-missing.json @@ -1,5 +1,5 @@ { - "description": "Bid request with user.ext.eids array element array element that does not contain uids", + "description": "Bid request with user.eids array element array element that does not contain uids", "mockBidRequest": { "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", "site": { @@ -37,13 +37,11 @@ } }, "user": { - "ext": { - "eids": [{ - "source": "source1" - }] - } + "eids": [{ + "source": "source1" + }] } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext.eids[0].uids must contain at least one element or be undefined\n" + "expectedErrorMessage": "Invalid request: request.user.eids[0].uids must contain at least one element or be undefined\n" } \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json index af04627c3a9..222ffb993b7 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json @@ -46,5 +46,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: expects \" or n, but found 1" + "expectedErrorMessage": "Invalid request: req.user.ext is invalid: expects \" or n, but found 1" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-duplicate.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-duplicate.json deleted file mode 100644 index 05cd7f34f8c..00000000000 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-duplicate.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "description": "Bid request where more than one request.user.ext.eids array elements share the same source field value", - "mockBidRequest": { - "id": "anyRequestID", - "site": { - "page": "prebid.org", - "publisher": { - "id": "anyPublisher" - } - }, - "imp": [{ - "id": "anyImpID", - "ext": { - "appnexus": { - "placementId": 42 - } - }, - "banner": { - "format": [{ - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 300 - } - ] - } - }], - "tmax": 1000, - "user": { - "ext": { - "eids": [{ - "source": "source1", - "uids": [{ - "id": "A" - }] - }, { - "source": "source1", - "uids": [{ - "id": "B" - }] - }] - } - } - }, - "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext.eids must contain unique sources\n" -} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json index b710d589ea5..ae9d72c8682 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json @@ -41,5 +41,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: expects \" or n, but found 2" + "expectedErrorMessage": "Invalid request: req.user.ext is invalid: expects \" or n, but found 2" } diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext-case-insensitive.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext-case-insensitive.json index 20997076af2..67633ab8e1a 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext-case-insensitive.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext-case-insensitive.json @@ -51,7 +51,7 @@ ] }, "ext": { - "appnexus": { + "APPnexus": { "placementId": 12883451 }, "districtm": { @@ -109,7 +109,7 @@ "price": 1.01 } ], - "seat": "appnexus" + "seat": "APPnexus" }, { "bid": [ @@ -137,4 +137,4 @@ "nbr": 0 }, "expectedReturnCode": 200 -} \ No newline at end of file +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json index 02dc6160d49..556a04fbec8 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json @@ -83,6 +83,8 @@ "name": "video", "version": "1.0" }, + "debug": true, + "integration": "managed", "targeting": { "includewinners": false, "pricegranularity": { diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/device-sua.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/device-sua.json new file mode 100644 index 00000000000..0f85f904166 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/device-sua.json @@ -0,0 +1,100 @@ +{ + "description": "Bid request defines an valid request.device.sua value", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 0.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org", + "publisher": { + "id": "a3de7af2-a86a-4043-a77b-c7e86744155e" + } + }, + "tmax": 1000, + "imp": [ + { + "id": "some-impression-id", + "ext": { + "appnexus": { + "placementId": 12883451 + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + } + } + ], + "device": { + "ua": "Mozilla", + "geo": { + "lat": 123.456, + "lon": 678.90, + "zip": "90210" + }, + "sua": { + "browsers": [ + { + "brand": "MS", + "ext": {} + }, + { + "brand": "MS", + "ext": {} + } + ], + "platform": { + "brand": "MS", + "ext": {} + }, + "model": "mac" + }, + "dnt": 1, + "lmt": 1 + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0, + "ext": { + "origbidcpm": 0, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } + } + ], + "seat": "appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.5-to-2.6-upconvert.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.5-to-2.6-upconvert.json new file mode 100644 index 00000000000..3ff235c4b30 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.5-to-2.6-upconvert.json @@ -0,0 +1,393 @@ +{ + "description": "Request with all 2.5 ext fields that were moved into 2.6 ortb fields", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 15 + }, + { + "bidderName": "rubicon", + "currency": "USD", + "price": 1.00 + } + ], + "bidderInfoOverrides": { + "appnexus": { + "openrtb": { + "version": "2.5" + } + }, + "rubicon": { + "openrtb": { + "version": "2.6" + } + } + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + }, + "is_rewarded_inventory": 1 + } + } + } + ], + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1YYY" + } + }, + "user": { + "ext": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + } + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + } + }, + "ext": {} + }, + "expectedValidatedBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org", + "ext": { + "amp": 0 + } + }, + "at": 1, + "device": { + "ip": "192.0.2.1" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + } + }, + "secure": 1, + "rwdd": 1 + } + ], + "regs": { + "gdpr": 1, + "us_privacy": "1YYY" + }, + "user": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + }, + "source": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + } + }, + "expectedMockBidderRequests": { + "appnexus": { + "id": "some-request-id", + "site": { + "page": "prebid.org", + "ext": { + "amp": 0 + } + }, + "at": 1, + "device": { + "ip": "192.0.2.1" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": 12883451 + }, + "prebid": { + "is_rewarded_inventory": 1 + } + }, + "secure": 1 + }], + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1YYY" + } + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + } + }, + "user": { + "ext": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + } + } + }, + "rubicon": { + "id": "some-request-id", + "site": { + "page": "prebid.org", + "ext": { + "amp": 0 + } + }, + "at": 1, + "device": { + "ip": "192.0.2.1" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + }, + "secure": 1, + "rwdd": 1 + }], + "regs": { + "gdpr": 1, + "us_privacy": "1YYY" + }, + "source": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + }, + "user": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + } + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 15, + "ext": { + "origbidcpm": 15, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } + } + ], + "seat": "appnexus" + }, + { + "bid": [ + { + "id": "rubicon-bid", + "impid": "some-impression-id", + "price": 1.00, + "ext": { + "origbidcpm": 1.00, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "rubicon" + }, + "type": "banner" + } + } + } + ], + "seat": "rubicon" + } + ], + "bidid": "test-bid-id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.6-to-2.5-downconvert.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.6-to-2.5-downconvert.json new file mode 100644 index 00000000000..28ba8a707e0 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.6-to-2.5-downconvert.json @@ -0,0 +1,417 @@ +{ + "description": "Request with all 2.5 ext fields that were moved into 2.6 ortb fields", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 15 + }, + { + "bidderName": "rubicon", + "currency": "USD", + "price": 1.00 + } + ], + "bidderInfoOverrides": { + "appnexus": { + "openrtb": { + "version": "2.5" + } + }, + "rubicon": { + "openrtb": { + "version": "2.6" + } + } + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org", + "inventorypartnerdomain": "any-domain" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + }, + "is_rewarded_inventory": 1 + } + }, + "refresh": { + "count": 10 + } + } + ], + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1YYY" + } + }, + "user": { + "ext": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + } + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + } + }, + "ext": {}, + "cattax": 20, + "acat": ["any-acat"] + }, + "expectedValidatedBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org", + "inventorypartnerdomain": "any-domain", + "ext": { + "amp": 0 + } + }, + "at": 1, + "device": { + "ip": "192.0.2.1" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + } + }, + "secure": 1, + "rwdd": 1, + "refresh": { + "count": 10 + } + } + ], + "regs": { + "gdpr": 1, + "us_privacy": "1YYY" + }, + "user": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + }, + "source": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + }, + "cattax": 20, + "acat": ["any-acat"] + }, + "expectedMockBidderRequests": { + "appnexus": { + "id": "some-request-id", + "site": { + "page": "prebid.org", + "inventorypartnerdomain": "any-domain", + "ext": { + "amp": 0 + } + }, + "at": 1, + "device": { + "ip": "192.0.2.1" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": 12883451 + }, + "prebid": { + "is_rewarded_inventory": 1 + } + }, + "refresh": { + "count": 10 + }, + "secure": 1 + }], + "regs": { + "ext": { + "gdpr": 1, + "us_privacy": "1YYY" + } + }, + "source": { + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + } + }, + "user": { + "ext": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + } + }, + "cattax": 20, + "acat": ["any-acat"] + }, + "rubicon": { + "id": "some-request-id", + "site": { + "page": "prebid.org", + "inventorypartnerdomain": "any-domain", + "ext": { + "amp": 0 + } + }, + "at": 1, + "device": { + "ip": "192.0.2.1" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + }, + "secure": 1, + "refresh": { + "count": 10 + }, + "rwdd": 1 + }], + "regs": { + "gdpr": 1, + "us_privacy": "1YYY" + }, + "source": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + } + ], + "ver": "2.0" + } + }, + "user": { + "consent": "some-consent-string", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + }, + "cattax": 20, + "acat": ["any-acat"] + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 15, + "ext": { + "origbidcpm": 15, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } + } + ], + "seat": "appnexus" + }, + { + "bid": [ + { + "id": "rubicon-bid", + "impid": "some-impression-id", + "price": 1.00, + "ext": { + "origbidcpm": 1.00, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "rubicon" + }, + "type": "banner" + } + } + } + ], + "seat": "rubicon" + } + ], + "bidid": "test-bid-id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json index 5a7dbd20747..a0c627b309b 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json @@ -47,7 +47,17 @@ { "id": "appnexus-bid", "impid": "some-impression-id", - "price": 0 + "price": 0, + "ext": { + "origbidcpm": 0, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } } ], "seat": "appnexus" diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/source-schain.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/source-schain.json new file mode 100644 index 00000000000..c25f7ca1c47 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/source-schain.json @@ -0,0 +1,91 @@ +{ + "description": "Bid request defines a valid request.source.schain.nodes value", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 0.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org", + "publisher": { + "id": "a3de7af2-a86a-4043-a77b-c7e86744155e" + } + }, + "tmax": 1000, + "imp": [ + { + "id": "some-impression-id", + "ext": { + "appnexus": { + "placementId": 12883451 + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + } + } + ], + "source": { + "fd": 1, + "tid": "abc123", + "pchain": "tag_placement", + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "asi", + "sid": "sid", + "rid": "rid", + "ext": {} + } + ], + "ver": "ver", + "ext": {} + } + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0, + "ext": { + "origbidcpm": 0, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "type": "banner" + } + } + } + ], + "seat": "appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/targeting-optional-all-false.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/targeting-optional-all-false.json new file mode 100644 index 00000000000..020a2ed849c --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/targeting-optional-all-false.json @@ -0,0 +1,69 @@ +{ + "description": "Targeting flags are all set to false, request is still valid, but no targeting data should be present in bids", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 0.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "targeting": { + "includewinners": false, + "includebidderkeys": false, + "includeformat": false + } + } + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0 + } + ], + "seat": "appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 + } \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/targeting-optional-includeformat-only.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/targeting-optional-includeformat-only.json new file mode 100644 index 00000000000..56eeaf4cea2 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/targeting-optional-includeformat-only.json @@ -0,0 +1,89 @@ +{ + "description": "Targeting flags are all undefined besides includeformat, request is still valid, defaults should come in for other flags so targeting data should be present in bid", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 1.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "targeting": { + "includeformat": true + } + } + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 1.00, + "ext": { + "origbidcpm": 1, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "appnexus" + }, + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_format": "banner", + "hb_format_appnexus": "banner", + "hb_pb": "1.00", + "hb_pb_appnexus": "1.00" + }, + "type": "banner" + } + } + } + ], + "seat": "appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 + } \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/targeting-optional-includeformat.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/targeting-optional-includeformat.json new file mode 100644 index 00000000000..2d7b801e20f --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/targeting-optional-includeformat.json @@ -0,0 +1,69 @@ +{ + "description": "Targeting flags are all set to false except for includeformat, request is still valid, but no targeting data should be present in bids", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 0.00 + } + ] + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "targeting": { + "includewinners": false, + "includebidderkeys": false, + "includeformat": true + } + } + } + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "some-impression-id", + "price": 0 + } + ], + "seat": "appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 + } \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/user-ext-eids-empty.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/user-ext-eids-empty.json index 9f047613277..ab633724e11 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/user-ext-eids-empty.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/user-ext-eids-empty.json @@ -1,56 +1,59 @@ { - "description": "Bid request with empty eids array in user.ext", - "mockBidRequest": { - "id": "some-request-id", - "site": { - "page": "prebid.org" + "description": "Bid request with empty eids array in user.ext", + "mockBidRequest": { + "id": "anyRequestID", + "site": { + "page": "prebid.org", + "publisher": { + "id": "anyPublisher" + } + }, + "imp": [ + { + "id": "anyImpID", + "ext": { + "appnexus": { + "placementId": 42 + } }, - "imp": [ + "banner": { + "format": [ { - "id": "some-impression-id", - "ext": { - "appnexus": { - "placementId": 123456 - } - }, - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - } - } - ], - "tmax": 1000, - "user": { - "ext": { - "eids": [] - } - } - }, - "expectedBidResponse": { - "id": "some-request-id", - "seatbid": [ + "w": 300, + "h": 250 + }, { - "bid": [ - { - "id": "appnexus-bid", - "impid": "some-impression-id", - "price": 0 - } - ], - "seat": "appnexus" + "w": 300, + "h": 600 } + ] + } + } + ], + "tmax": 1000, + "user": { + "ext": { + "eids": [] + } + } + }, + "expectedBidResponse": { + "id": "anyRequestID", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "anyImpID", + "price": 0 + } ], - "bidid": "test bid id", - "cur": "USD", - "nbr": 0 - }, - "expectedReturnCode": 200 + "seat": "appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 } \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/user-ext-eids-source-duplicate.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/user-ext-eids-source-duplicate.json new file mode 100644 index 00000000000..e4ee6c72e49 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/user-ext-eids-source-duplicate.json @@ -0,0 +1,76 @@ +{ + "description": "Bid request where more than one request.user.ext.eids array elements share the same source field value", + "mockBidRequest": { + "id": "anyRequestID", + "site": { + "page": "prebid.org", + "publisher": { + "id": "anyPublisher" + } + }, + "imp": [ + { + "id": "anyImpID", + "ext": { + "appnexus": { + "placementId": 42 + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + } + } + ], + "tmax": 1000, + "user": { + "ext": { + "eids": [ + { + "source": "source1", + "uids": [ + { + "id": "A" + } + ] + }, + { + "source": "source1", + "uids": [ + { + "id": "B" + } + ] + } + ] + } + } + }, + "expectedBidResponse": { + "id": "anyRequestID", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "anyImpID", + "price": 0 + } + ], + "seat": "appnexus" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/user-ext-prebid-buyeruids.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/user-ext-prebid-buyeruids.json new file mode 100644 index 00000000000..a6576c7b3f1 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/user-ext-prebid-buyeruids.json @@ -0,0 +1,170 @@ +{ + "description": "Bid request with user.ext.prebid.buyeruids object", + "config": { + "mockBidders": [ + { + "bidderName": "appnexus", + "currency": "USD", + "price": 1.00 + }, + { + "bidderName": "pubmatic", + "currency": "USD", + "price": 2.00 + } + ], + "bidderInfoOverrides": { + "appnexus": { + "openrtb": { + "version": "2.5" + } + }, + "pubmatic": { + "openrtb": { + "version": "2.6" + } + } + } + }, + "mockBidRequest": { + "id": "request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + }, + "pubmatic": { + "publisherId": "123" + } + } + } + ], + "user": { + "consent": "some-consent-string", + "ext": { + "prebid": { + "buyeruids": { + "appnexus": "appnexus-buyeruid", + "pubmatic": "pubmatic-buyeruid" + } + } + } + } + }, + "expectedMockBidderRequests": { + "appnexus": { + "id": "request-id", + "site": { + "page": "prebid.org", + "ext": { + "amp": 0 + } + }, + "at": 1, + "device": { + "ip": "192.0.2.1" + }, + "imp": [ + { + "id": "imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": 12883451 + } + }, + "secure": 1 + }], + "user": { + "buyeruid": "appnexus-buyeruid", + "ext": { + "consent": "some-consent-string" + } + } + }, + "pubmatic": { + "id": "request-id", + "site": { + "page": "prebid.org", + "ext": { + "amp": 0 + } + }, + "at": 1, + "device": { + "ip": "192.0.2.1" + }, + "imp": [ + { + "id": "imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "123" + } + }, + "secure": 1 + }], + "user": { + "buyeruid": "pubmatic-buyeruid", + "consent": "some-consent-string" + } + } + }, + "expectedBidResponse": { + "id": "request-id", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "imp-id", + "price": 1.0 + } + ], + "seat": "appnexus" + }, + { + "bid": [ + { + "id": "pubmatic-bid", + "impid": "imp-id", + "price": 2.0 + } + ], + "seat": "pubmatic" + } + ], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict.json index 3d6a0774b9d..9565e41af1f 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict.json @@ -37,10 +37,7 @@ "regs": { "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1NYN", "gpp_sid": [6], - "gdpr": 1, - "ext": { - "us_privacy": "1YYY" - } + "gdpr": 1 }, "user": { "consent": "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict2.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict2.json index 2102a8cd44b..54477ed0986 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict2.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict2.json @@ -37,10 +37,7 @@ "regs": { "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1NYN", "gpp_sid": [2,6], - "gdpr": 1, - "ext": { - "us_privacy": "1YYY" - } + "gdpr": 1 }, "user": { "consent": "Invalid", diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-matching-bidder-name.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-matching-bidder-name.json new file mode 100644 index 00000000000..49576023b04 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-matching-bidder-name.json @@ -0,0 +1,39 @@ +{ + "description": "request with impression with stored bid response with case matching bidder name", + "mockBidRequest": { + "id": "request-with-stored-resp", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id3", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + }, + "prebid": { + "storedbidresponse": [ + { + "bidder": "appnexus", + "id": "bidResponseId3" + } + ] + } + } + } + ], + "user": { + "yob": 1989 + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-insensitive-bidder-name.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-not-matching-bidder-name.json similarity index 94% rename from endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-insensitive-bidder-name.json rename to endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-not-matching-bidder-name.json index e7b688d4d83..bcebe81472d 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-insensitive-bidder-name.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-case-not-matching-bidder-name.json @@ -1,5 +1,5 @@ { - "description": "request with impression with stored bid response with sensitive bidder name", + "description": "request with impression with stored bid response with case not matching bidder name", "mockBidRequest": { "id": "request-with-stored-resp", "site": { diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-non-existing-bidder-name.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-non-existing-bidder-name.json new file mode 100644 index 00000000000..d8685307e20 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp-non-existing-bidder-name.json @@ -0,0 +1,34 @@ +{ + "description": "request with impression with stored bid response", + "mockBidRequest": { + "id": "request-with-stored-resp", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id1", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidderABC": {}, + "prebid": { + "storedbidresponse": [ + {"bidder":"bidderABC", "id": "bidResponseId1"} + ] + } + } + } + ], + "user": { + "yob": 1989 + } + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp.json index e72cce49355..692a34c4f41 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-bid-resp.json @@ -17,12 +17,10 @@ ] }, "ext": { - "appnexus": { - "placementId": 12883451 - }, + "telaria": {}, "prebid": { "storedbidresponse": [ - {"bidder":"testBidder1", "id": "bidResponseId1"} + {"bidder":"telaria", "id": "bidResponseId1"} ] } } diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json index 1e2f58cbfd2..9f0e78be352 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json @@ -76,7 +76,6 @@ } ] }, - "mediatypepricegranularity": {}, "includewinners": true, "includebidderkeys": true }, diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-backward-compatible-merge.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-promotion.json similarity index 94% rename from endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-backward-compatible-merge.json rename to endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-promotion.json index 908e20e5b79..d1d20404911 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-backward-compatible-merge.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-promotion.json @@ -32,8 +32,8 @@ "profile": 1234 } }, - "prebid": { - "bidder": {} + "arbitraryObject": { + "arbitraryField": 1232 } } } @@ -74,7 +74,6 @@ } ] }, - "mediatypepricegranularity": {}, "includewinners": true, "includebidderkeys": true }, @@ -108,6 +107,9 @@ } } } + }, + "arbitraryObject": { + "arbitraryField": 1232 } }, "expectedReturnCode": 200 diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json index 311d4e11485..27ee9da486c 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json @@ -69,7 +69,6 @@ } ] }, - "mediatypepricegranularity": {}, "includewinners": true, "includebidderkeys": true }, diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-stored-bid-responses.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-stored-bid-responses.json index 5906eb9149c..09b4bc57746 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-stored-bid-responses.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-stored-bid-responses.json @@ -17,12 +17,12 @@ ] }, "ext": { - "appnexus": { + "telaria": { "placementId": 12883451 }, "prebid": { "storedbidresponse": [ - {"bidder":"testBidder1", "id": "bidResponseId1"} + {"bidder":"telaria", "id": "bidResponseId1"} ] } } @@ -38,12 +38,10 @@ ] }, "ext": { - "appnexus": { - "placementId": 12883451 - }, + "amx": {}, "prebid": { "storedbidresponse": [ - {"bidder":"testBidder2", "id": "bidResponseId2"} + {"bidder":"amx", "id": "bidResponseId2"} ] } } diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-with-and-without-stored-bid-responses.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-with-and-without-stored-bid-responses.json index 6122e4df066..5387a9d61ae 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-with-and-without-stored-bid-responses.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-two-imps-with-and-without-stored-bid-responses.json @@ -33,12 +33,12 @@ ] }, "ext": { - "appnexus": { + "amx": { "placementId": 12883451 }, "prebid": { "storedbidresponse": [ - {"bidder":"testBidder2", "id": "bidResponseId2"} + {"bidder":"amx", "id": "bidResponseId2"} ] } } 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 index 2ccdfb7ccdc..df2c426d0c3 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json @@ -35,9 +35,7 @@ } ], "regs": { - "ext": { - "us_privacy": "{invalid}" - } + "us_privacy": "{invalid}" }, "user": { "ext": {} diff --git a/endpoints/openrtb2/sample-requests/video/video_invalid_sample_negative_tmax.json b/endpoints/openrtb2/sample-requests/video/video_invalid_sample_negative_tmax.json new file mode 100644 index 00000000000..e1dc02314f8 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/video/video_invalid_sample_negative_tmax.json @@ -0,0 +1,87 @@ +{ + "description": "video request with negative tmax value. Expect error", + + "requestPayload": { + "tmax": -2, + "storedrequestid": "80ce30c53c16e6ede735f123ef6e32361bfc7b22", + "podconfig": { + "durationrangesec": [ + 30 + ], + "requireexactduration": true, + "pods": [{ + "podid": 1, + "adpoddurationsec": 180, + "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6" + }, + { + "podid": 2, + "adpoddurationsec": 150, + "configid": "8b452b41-2681-4a20-9086-6f16ffad7773" + } + ] + }, + "site": { + "page": "prebid.com" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "yob": 1991, + "gender": "F", + "keywords": "Hotels, Travelling", + "ext": { + "prebid": { + "buyeruids": { + "appnexus": "unique_id_an", + "rubicon": "unique_id_rubi" + } + } + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "AA000DFE74168477C70D291f574D344790E0BB11", + "lmt": 44, + "os": "mac os", + "w": 640, + "h": 480, + "didsha1": "didsha1", + "didmd5": "didmd5", + "dpidsha1": "dpidsha1", + "dpidmd5": "dpidmd5", + "macsha1": "macsha1", + "macmd5": "macmd5" + }, + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "" + }, + "video": { + "w": 640, + "h": 480, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, 3, 5, 6 + ] + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "livestream": 0 + }, + "cacheconfig": { + "ttl": 42 + } + } + } \ No newline at end of file diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go index e8506813eef..e869ded80b8 100644 --- a/endpoints/openrtb2/test_utils.go +++ b/endpoints/openrtb2/test_utils.go @@ -16,30 +16,31 @@ import ( "github.com/buger/jsonparser" "github.com/julienschmidt/httprouter" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/openrtb/v19/openrtb3" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/analytics" - analyticsBuild "github.com/prebid/prebid-server/analytics/build" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - pbc "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/uuidutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/openrtb/v20/openrtb3" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/analytics" + analyticsBuild "github.com/prebid/prebid-server/v3/analytics/build" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/exchange" + "github.com/prebid/prebid-server/v3/experiment/adscert" + "github.com/prebid/prebid-server/v3/gdpr" + "github.com/prebid/prebid-server/v3/hooks" + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/hooks/hookstage" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/metrics" + metricsConfig "github.com/prebid/prebid-server/v3/metrics/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/ortb" + pbc "github.com/prebid/prebid-server/v3/prebid_cache_client" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v3/util/iputil" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/uuidutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" ) @@ -63,15 +64,16 @@ const ( type testCase struct { // Common - endpointType int - Description string `json:"description"` - Config *testConfigValues `json:"config"` - BidRequest json.RawMessage `json:"mockBidRequest"` - ExpectedValidatedBidReq json.RawMessage `json:"expectedValidatedBidRequest"` - ExpectedReturnCode int `json:"expectedReturnCode,omitempty"` - ExpectedErrorMessage string `json:"expectedErrorMessage"` - Query string `json:"query"` - planBuilder hooks.ExecutionPlanBuilder + endpointType int + Description string `json:"description"` + Config *testConfigValues `json:"config"` + BidRequest json.RawMessage `json:"mockBidRequest"` + ExpectedValidatedBidReq json.RawMessage `json:"expectedValidatedBidRequest"` + ExpectedMockBidderRequests map[string]json.RawMessage `json:"expectedMockBidderRequests"` + ExpectedReturnCode int `json:"expectedReturnCode,omitempty"` + ExpectedErrorMessage string `json:"expectedErrorMessage"` + Query string `json:"query"` + planBuilder hooks.ExecutionPlanBuilder // "/openrtb2/auction" endpoint JSON test info ExpectedBidResponse json.RawMessage `json:"expectedBidResponse"` @@ -83,13 +85,20 @@ type testCase struct { } type testConfigValues struct { - AccountRequired bool `json:"accountRequired"` - AliasJSON string `json:"aliases"` - BlacklistedApps []string `json:"blacklistedApps"` - DisabledAdapters []string `json:"disabledAdapters"` - CurrencyRates map[string]map[string]float64 `json:"currencyRates"` - MockBidders []mockBidderHandler `json:"mockBidders"` - RealParamsValidator bool `json:"realParamsValidator"` + AccountRequired bool `json:"accountRequired"` + AliasJSON string `json:"aliases"` + BlockedApps []string `json:"blockedApps"` + DisabledAdapters []string `json:"disabledAdapters"` + CurrencyRates map[string]map[string]float64 `json:"currencyRates"` + MockBidders []mockBidderHandler `json:"mockBidders"` + RealParamsValidator bool `json:"realParamsValidator"` + BidderInfos map[string]bidderInfoOverrides `json:"bidderInfoOverrides"` +} +type bidderInfoOverrides struct { + OpenRTB *OpenRTBInfo `json:"openrtb"` +} +type OpenRTBInfo struct { + Version string `json:"version"` } type brokenExchange struct{} @@ -941,6 +950,7 @@ type mockBidderHandler struct { Currency string `json:"currency"` Price float64 `json:"price"` DealID string `json:"dealid"` + Seat string `json:"seat"` } func (b mockBidderHandler) bid(w http.ResponseWriter, req *http.Request) { @@ -999,6 +1009,8 @@ func (b mockBidderHandler) bid(w http.ResponseWriter, req *http.Request) { type mockAdapter struct { mockServerURL string Server config.Server + seat string + requestData [][]byte } func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { @@ -1009,7 +1021,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co return adapter, nil } -func (a mockAdapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *mockAdapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var requests []*adapters.RequestData var errors []error @@ -1029,11 +1041,12 @@ func (a mockAdapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *ada Body: requestJSON, } requests = append(requests, requestData) + a.requestData = append(a.requestData, requestData.Body) } return requests, errors } -func (a mockAdapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *mockAdapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { if responseData.StatusCode != http.StatusOK { switch responseData.StatusCode { case http.StatusNoContent: @@ -1064,6 +1077,9 @@ func (a mockAdapter) MakeBids(request *openrtb2.BidRequest, requestData *adapter Bid: &seatBid.Bid[i], BidType: openrtb_ext.BidTypeBanner, } + if len(a.seat) > 0 { + b.Seat = openrtb_ext.BidderName(a.seat) + } rv.Bids = append(rv.Bids, b) } } @@ -1091,6 +1107,24 @@ func getBidderInfos(disabledAdapters []string, biddersNames []openrtb_ext.Bidder return biddersInfos } +func enableBidders(bidderInfos config.BidderInfos) { + for name, bidderInfo := range bidderInfos { + if bidderInfo.Disabled { + bidderInfo.Disabled = false + bidderInfos[name] = bidderInfo + } + } +} + +func disableBidders(disabledAdapters []string, bidderInfos config.BidderInfos) { + for _, disabledAdapter := range disabledAdapters { + if bidderInfo, ok := bidderInfos[disabledAdapter]; ok { + bidderInfo.Disabled = true + bidderInfos[disabledAdapter] = bidderInfo + } + } +} + func newBidderInfo(isDisabled bool) config.BidderInfo { return config.BidderInfo{ Disabled: isDisabled, @@ -1135,27 +1169,40 @@ func parseTestData(fileData []byte, testFile string) (testCase, error) { return parsedTestData, fmt.Errorf("Test case %s should come with either a valid expectedBidResponse or a valid expectedErrorMessage, not both.", testFile) } + // Get optional expected validated bid request + parsedTestData.ExpectedValidatedBidReq, _, _, err = jsonparser.Get(fileData, "expectedValidatedBidRequest") + + // Get optional expected mock bidder requests + jsonExpectedMockBidderRequests, _, _, err := jsonparser.Get(fileData, "expectedMockBidderRequests") + if err == nil && jsonExpectedMockBidderRequests != nil { + parsedTestData.ExpectedMockBidderRequests = make(map[string]json.RawMessage) + if err = jsonutil.UnmarshalValid(jsonExpectedMockBidderRequests, &parsedTestData.ExpectedMockBidderRequests); err != nil { + return parsedTestData, fmt.Errorf("Error unmarshaling root.expectedMockBidderRequests from file %s. Desc: %v.", testFile, err) + } + } + parsedTestData.ExpectedReturnCode = int(parsedReturnCode) return parsedTestData, nil } -func (tc *testConfigValues) getBlacklistedAppMap() map[string]bool { - var blacklistedAppMap map[string]bool +func (tc *testConfigValues) getBlockedAppLookup() map[string]bool { + var blockedAppLookup map[string]bool - if len(tc.BlacklistedApps) > 0 { - blacklistedAppMap = make(map[string]bool, len(tc.BlacklistedApps)) - for _, app := range tc.BlacklistedApps { - blacklistedAppMap[app] = true + if len(tc.BlockedApps) > 0 { + blockedAppLookup = make(map[string]bool, len(tc.BlockedApps)) + for _, app := range tc.BlockedApps { + blockedAppLookup[app] = true } } - return blacklistedAppMap + return blockedAppLookup } // exchangeTestWrapper is a wrapper that asserts the openrtb2 bid request just before the HoldAuction call type exchangeTestWrapper struct { ex exchange.Exchange actualValidatedBidReq *openrtb2.BidRequest + adapters map[openrtb_ext.BidderName]exchange.AdaptedBidder } func (te *exchangeTestWrapper) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) { @@ -1173,16 +1220,16 @@ func (te *exchangeTestWrapper) HoldAuction(ctx context.Context, r *exchange.Auct } // buildTestExchange returns an exchange with mock bidder servers and mock currency conversion server -func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.BidderName]exchange.AdaptedBidder, mockBidServersArray []*httptest.Server, mockCurrencyRatesServer *httptest.Server, bidderInfos config.BidderInfos, cfg *config.Configuration, met metrics.MetricsEngine, mockFetcher stored_requests.CategoryFetcher) (exchange.Exchange, []*httptest.Server) { +func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.BidderName]exchange.AdaptedBidder, mockBidServersArray []*httptest.Server, mockCurrencyRatesServer *httptest.Server, bidderInfos config.BidderInfos, cfg *config.Configuration, met metrics.MetricsEngine, mockFetcher stored_requests.CategoryFetcher, requestValidator ortb.RequestValidator) (exchange.Exchange, []*httptest.Server) { if len(testCfg.MockBidders) == 0 { testCfg.MockBidders = append(testCfg.MockBidders, mockBidderHandler{BidderName: "appnexus", Currency: "USD", Price: 0.00}) } for _, mockBidder := range testCfg.MockBidders { bidServer := httptest.NewServer(http.HandlerFunc(mockBidder.bid)) - bidderAdapter := mockAdapter{mockServerURL: bidServer.URL} + bidderAdapter := mockAdapter{mockServerURL: bidServer.URL, seat: mockBidder.Seat} bidderName := openrtb_ext.BidderName(mockBidder.BidderName) - adapterMap[bidderName] = exchange.AdaptBidder(bidderAdapter, bidServer.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, bidderName, nil, "") + adapterMap[bidderName] = exchange.AdaptBidder(&bidderAdapter, bidServer.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, bidderName, nil, "") mockBidServersArray = append(mockBidServersArray, bidServer) } @@ -1194,8 +1241,10 @@ func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.Bid }.Builder testExchange := exchange.NewExchange(adapterMap, + &wellBehavedCache{}, cfg, + requestValidator, nil, met, bidderInfos, @@ -1204,10 +1253,12 @@ func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.Bid mockFetcher, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), + nil, ) testExchange = &exchangeTestWrapper{ - ex: testExchange, + ex: testExchange, + adapters: adapterMap, } return testExchange, mockBidServersArray @@ -1230,9 +1281,24 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han paramValidator = mockBidderParamValidator{} } - bidderInfos := getBidderInfos(test.Config.DisabledAdapters, openrtb_ext.CoreBidderNames()) + bidderInfos, _ := config.LoadBidderInfoFromDisk("../../static/bidder-info") + for bidder, overrides := range test.Config.BidderInfos { + if bi, ok := bidderInfos[bidder]; ok { + if overrides.OpenRTB != nil && len(overrides.OpenRTB.Version) > 0 { + if bi.OpenRTB == nil { + bi.OpenRTB = &config.OpenRTBInfo{} + } + bi.OpenRTB.Version = overrides.OpenRTB.Version + bidderInfos[bidder] = bi + } + } + } + + enableBidders(bidderInfos) + disableBidders(test.Config.DisabledAdapters, bidderInfos) bidderMap := exchange.GetActiveBidders(bidderInfos) disabledBidders := exchange.GetDisabledBidderWarningMessages(bidderInfos) + requestValidator := ortb.NewRequestValidator(bidderMap, disabledBidders, paramValidator) met := &metricsConfig.NilMetricsEngine{} mockFetcher := empty_fetcher.EmptyFetcher{} @@ -1248,7 +1314,7 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han } mockCurrencyRatesServer := httptest.NewServer(http.HandlerFunc(mockCurrencyConversionService.handle)) - testExchange, mockBidServersArray := buildTestExchange(test.Config, adapterMap, mockBidServersArray, mockCurrencyRatesServer, bidderInfos, cfg, met, mockFetcher) + testExchange, mockBidServersArray := buildTestExchange(test.Config, adapterMap, mockBidServersArray, mockCurrencyRatesServer, bidderInfos, cfg, met, mockFetcher, requestValidator) var storedRequestFetcher stored_requests.Fetcher if len(test.StoredRequest) > 0 { @@ -1266,8 +1332,9 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han accountFetcher := &mockAccountFetcher{ data: map[string]json.RawMessage{ - "malformed_acct": json.RawMessage(`{"disabled":"invalid type"}`), - "disabled_acct": json.RawMessage(`{"disabled":true}`), + "malformed_acct": json.RawMessage(`{"disabled":"invalid type"}`), + "disabled_acct": json.RawMessage(`{"disabled":true}`), + "alternate_bidder_code_acct": json.RawMessage(`{"disabled":false,"alternatebiddercodes":{"enabled":true,"bidders":{"appnexus":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}`), }, } @@ -1276,7 +1343,7 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han planBuilder = hooks.EmptyPlanBuilder{} } - var endpointBuilder func(uuidutil.UUIDGenerator, exchange.Exchange, openrtb_ext.BidderParamValidator, stored_requests.Fetcher, stored_requests.AccountFetcher, *config.Configuration, metrics.MetricsEngine, analytics.Runner, map[string]string, []byte, map[string]openrtb_ext.BidderName, stored_requests.Fetcher, hooks.ExecutionPlanBuilder, *exchange.TmaxAdjustmentsPreprocessed) (httprouter.Handle, error) + var endpointBuilder func(uuidutil.UUIDGenerator, exchange.Exchange, ortb.RequestValidator, stored_requests.Fetcher, stored_requests.AccountFetcher, *config.Configuration, metrics.MetricsEngine, analytics.Runner, map[string]string, []byte, map[string]openrtb_ext.BidderName, stored_requests.Fetcher, hooks.ExecutionPlanBuilder, *exchange.TmaxAdjustmentsPreprocessed) (httprouter.Handle, error) switch test.endpointType { case AMP_ENDPOINT: @@ -1288,7 +1355,7 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han endpoint, err := endpointBuilder( fakeUUIDGenerator{}, testExchange, - paramValidator, + requestValidator, storedRequestFetcher, accountFetcher, cfg, @@ -1399,10 +1466,10 @@ func (p *fakePermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return true, nil } -func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) { +func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) gdpr.AuctionPermissions { return gdpr.AuctionPermissions{ AllowBidRequest: true, - }, nil + } } type mockPlanBuilder struct { @@ -1578,7 +1645,7 @@ var entryPointHookUpdate = hooks.HookWrapper[hookstage.Entrypoint]{ ch := hookstage.ChangeSet[hookstage.EntrypointPayload]{} ch.AddMutation(func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) { - body, err := jsonpatch.MergePatch(payload.Body, []byte(`{"tmax":50}`)) + body, err := jsonpatch.MergePatch(payload.Body, []byte(`{"tmax":600}`)) if err == nil { payload.Body = body } diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 94455e0cb52..cf18840fbd6 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -16,29 +16,29 @@ import ( "github.com/gofrs/uuid" "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/ortb" - "github.com/prebid/prebid-server/privacy" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/hooks" + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/ortb" + "github.com/prebid/prebid-server/v3/privacy" jsonpatch "gopkg.in/evanphx/json-patch.v4" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" - "github.com/prebid/prebid-server/util/uuidutil" - "github.com/prebid/prebid-server/version" + accountService "github.com/prebid/prebid-server/v3/account" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/exchange" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/prebid_cache_client" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v3/usersync" + "github.com/prebid/prebid-server/v3/util/iputil" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" + "github.com/prebid/prebid-server/v3/util/uuidutil" + "github.com/prebid/prebid-server/v3/version" ) var defaultRequestTimeout int64 = 5000 @@ -46,7 +46,7 @@ var defaultRequestTimeout int64 = 5000 func NewVideoEndpoint( uuidGenerator uuidutil.UUIDGenerator, ex exchange.Exchange, - validator openrtb_ext.BidderParamValidator, + requestValidator ortb.RequestValidator, requestsById stored_requests.Fetcher, videoFetcher stored_requests.Fetcher, accounts stored_requests.AccountFetcher, @@ -60,7 +60,7 @@ func NewVideoEndpoint( tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed, ) (httprouter.Handle, error) { - if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || met == nil { + if ex == nil || requestValidator == nil || requestsById == nil || accounts == nil || cfg == nil || met == nil { return nil, errors.New("NewVideoEndpoint requires non-nil arguments.") } @@ -76,7 +76,7 @@ func NewVideoEndpoint( return httprouter.Handle((&endpointDeps{ uuidGenerator, ex, - validator, + requestValidator, requestsById, videoFetcher, accounts, @@ -165,6 +165,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re }() w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver)) + setBrowsingTopicsHeader(w, r) lr := &io.LimitedReader{ R: r.Body, @@ -259,16 +260,12 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re // all code after this line should use the bidReqWrapper instead of bidReq directly bidReqWrapper := &openrtb_ext.RequestWrapper{BidRequest: bidReq} - // Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers). - deps.setFieldsImplicitly(r, bidReqWrapper) - - if err := ortb.SetDefaults(bidReqWrapper); err != nil { - handleError(&labels, w, errL, &vo, &debugLog) + if err := openrtb_ext.ConvertUpTo26(bidReqWrapper); err != nil { + handleError(&labels, w, []error{err}, &vo, &debugLog) return } - errL = deps.validateRequest(bidReqWrapper, false, false, nil, false) - if errortypes.ContainsFatalError(errL) { + if err := ortb.SetDefaults(bidReqWrapper); err != nil { handleError(&labels, w, errL, &vo, &debugLog) return } @@ -306,8 +303,22 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re return } + // Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers). + if errs := deps.setFieldsImplicitly(r, bidReqWrapper, account); len(errs) > 0 { + errL = append(errL, errs...) + } + + errs := deps.validateRequest(account, r, bidReqWrapper, false, false, nil, false) + errL = append(errL, errs...) + if errortypes.ContainsFatalError(errL) { + handleError(&labels, w, errL, &vo, &debugLog) + return + } + activityControl = privacy.NewActivityControl(&account.Privacy) + warnings := errortypes.WarningOnly(errL) + secGPC := r.Header.Get("Sec-GPC") auctionRequest := &exchange.AuctionRequest{ BidRequestWrapper: bidReqWrapper, @@ -316,6 +327,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re RequestType: labels.RType, StartTime: start, LegacyLabels: labels, + Warnings: warnings, GlobalPrivacyControlHeader: secGPC, PubID: labels.PubID, HookExecutor: hookexecution.EmptyHookExecutor{}, @@ -404,9 +416,9 @@ func handleError(labels *metrics.Labels, w http.ResponseWriter, errL []error, vo var status int = http.StatusInternalServerError for _, er := range errL { erVal := errortypes.ReadCode(er) - if erVal == errortypes.BlacklistedAppErrorCode || erVal == errortypes.AccountDisabledErrorCode { + if erVal == errortypes.BlockedAppErrorCode || erVal == errortypes.AccountDisabledErrorCode { status = http.StatusServiceUnavailable - labels.RequestStatus = metrics.RequestStatusBlacklisted + labels.RequestStatus = metrics.RequestStatusBlockedApp break } else if erVal == errortypes.AcctRequiredErrorCode { status = http.StatusBadRequest @@ -496,7 +508,7 @@ func createImpressionTemplate(imp openrtb2.Imp, video *openrtb2.Video) openrtb2. } func (deps *endpointDeps) loadStoredImp(storedImpId string) (openrtb2.Imp, []error) { - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(storedRequestTimeoutMillis)*time.Millisecond) + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(deps.cfg.StoredRequestsTimeout)*time.Millisecond) defer cancel() impr := openrtb2.Imp{} @@ -806,8 +818,8 @@ func (deps *endpointDeps) validateVideoRequest(req *openrtb_ext.BidRequestVideo) errL = append(errL, err) } else if req.App != nil { if req.App.ID != "" { - if _, found := deps.cfg.BlacklistedAppMap[req.App.ID]; found { - err := &errortypes.BlacklistedApp{Message: fmt.Sprintf("Prebid-server does not process requests from App ID: %s", req.App.ID)} + if _, found := deps.cfg.BlockedAppsLookup[req.App.ID]; found { + err := &errortypes.BlockedApp{Message: fmt.Sprintf("Prebid-server does not process requests from App ID: %s", req.App.ID)} errL = append(errL, err) return errL, podErrors } diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 281c80b204d..93f7f6fcf33 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -11,23 +11,24 @@ import ( "strings" "testing" - "github.com/prebid/prebid-server/analytics" - analyticsBuild "github.com/prebid/prebid-server/analytics/build" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/metrics" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" - - "github.com/prebid/openrtb/v19/adcom1" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v3/analytics" + analyticsBuild "github.com/prebid/prebid-server/v3/analytics/build" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/exchange" + "github.com/prebid/prebid-server/v3/hooks" + "github.com/prebid/prebid-server/v3/metrics" + metricsConfig "github.com/prebid/prebid-server/v3/metrics/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/ortb" + "github.com/prebid/prebid-server/v3/prebid_cache_client" + "github.com/prebid/prebid-server/v3/privacy" + "github.com/prebid/prebid-server/v3/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" + + "github.com/prebid/openrtb/v20/adcom1" + "github.com/prebid/openrtb/v20/openrtb2" gometrics "github.com/rcrowley/go-metrics" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -53,8 +54,8 @@ func TestVideoEndpointImpressionsNumber(t *testing.T) { } assert.Len(t, ex.lastRequest.Imp, 11, "Incorrect number of impressions in request") - assert.Equal(t, string(ex.lastRequest.Site.Page), "prebid.com", "Incorrect site page in request") - assert.Equal(t, ex.lastRequest.Site.Content.Series, "TvName", "Incorrect site content series in request") + assert.Equal(t, "prebid.com", string(ex.lastRequest.Site.Page), "Incorrect site page in request") + assert.Equal(t, "TvName", ex.lastRequest.Site.Content.Series, "Incorrect site content series in request") assert.Len(t, resp.AdPods, 5, "Incorrect number of Ad Pods in response") assert.Len(t, resp.AdPods[0].Targeting, 4, "Incorrect Targeting data in response") @@ -63,8 +64,8 @@ func TestVideoEndpointImpressionsNumber(t *testing.T) { assert.Len(t, resp.AdPods[3].Targeting, 1, "Incorrect Targeting data in response") assert.Len(t, resp.AdPods[4].Targeting, 3, "Incorrect Targeting data in response") - assert.Equal(t, resp.AdPods[4].Targeting[0].HbPbCatDur, "20.00_395_30s", "Incorrect number of Ad Pods in response") - assert.Equal(t, resp.AdPods[0].Targeting[0].HbDeal, "ABC_123", "If DealID exists in bid response, hb_deal targeting needs to be added to resp") + assert.Equal(t, "20.00_395_30s", resp.AdPods[4].Targeting[0].HbPbCatDur, "Incorrect number of Ad Pods in response") + assert.Equal(t, "ABC_123", resp.AdPods[0].Targeting[0].HbDeal, "If DealID exists in bid response, hb_deal targeting needs to be added to resp") } func TestVideoEndpointImpressionsDuration(t *testing.T) { @@ -86,21 +87,21 @@ func TestVideoEndpointImpressionsDuration(t *testing.T) { assert.True(t, *extData.Prebid.Targeting.IncludeBidderKeys, "Request ext incorrect: IncludeBidderKeys should be true ") assert.Len(t, ex.lastRequest.Imp, 22, "Incorrect number of impressions in request") - assert.Equal(t, ex.lastRequest.Imp[0].ID, "1_0", "Incorrect impression id in request") - assert.Equal(t, ex.lastRequest.Imp[0].Video.MaxDuration, int64(15), "Incorrect impression max duration in request") - assert.Equal(t, ex.lastRequest.Imp[0].Video.MinDuration, int64(15), "Incorrect impression min duration in request") + assert.Equal(t, "1_0", ex.lastRequest.Imp[0].ID, "Incorrect impression id in request") + assert.Equal(t, int64(15), ex.lastRequest.Imp[0].Video.MaxDuration, "Incorrect impression max duration in request") + assert.Equal(t, int64(15), ex.lastRequest.Imp[0].Video.MinDuration, "Incorrect impression min duration in request") - assert.Equal(t, ex.lastRequest.Imp[6].ID, "1_6", "Incorrect impression id in request") - assert.Equal(t, ex.lastRequest.Imp[6].Video.MaxDuration, int64(30), "Incorrect impression max duration in request") - assert.Equal(t, ex.lastRequest.Imp[6].Video.MinDuration, int64(30), "Incorrect impression min duration in request") + assert.Equal(t, "1_6", ex.lastRequest.Imp[6].ID, "Incorrect impression id in request") + assert.Equal(t, int64(30), ex.lastRequest.Imp[6].Video.MaxDuration, "Incorrect impression max duration in request") + assert.Equal(t, int64(30), ex.lastRequest.Imp[6].Video.MinDuration, "Incorrect impression min duration in request") - assert.Equal(t, ex.lastRequest.Imp[12].ID, "2_0", "Incorrect impression id in request") - assert.Equal(t, ex.lastRequest.Imp[12].Video.MaxDuration, int64(15), "Incorrect impression max duration in request") - assert.Equal(t, ex.lastRequest.Imp[12].Video.MinDuration, int64(15), "Incorrect impression min duration in request") + assert.Equal(t, "2_0", ex.lastRequest.Imp[12].ID, "Incorrect impression id in request") + assert.Equal(t, int64(15), ex.lastRequest.Imp[12].Video.MaxDuration, "Incorrect impression max duration in request") + assert.Equal(t, int64(15), ex.lastRequest.Imp[12].Video.MinDuration, "Incorrect impression min duration in request") - assert.Equal(t, ex.lastRequest.Imp[17].ID, "2_5", "Incorrect impression id in request") - assert.Equal(t, ex.lastRequest.Imp[17].Video.MaxDuration, int64(30), "Incorrect impression max duration in request") - assert.Equal(t, ex.lastRequest.Imp[17].Video.MinDuration, int64(30), "Incorrect impression min duration in request") + assert.Equal(t, "2_5", ex.lastRequest.Imp[17].ID, "Incorrect impression id in request") + assert.Equal(t, int64(30), ex.lastRequest.Imp[17].Video.MaxDuration, "Incorrect impression max duration in request") + assert.Equal(t, int64(30), ex.lastRequest.Imp[17].Video.MinDuration, "Incorrect impression min duration in request") } func TestCreateBidExtension(t *testing.T) { @@ -139,8 +140,8 @@ func TestCreateBidExtension(t *testing.T) { if err := jsonutil.UnmarshalValid(res, &resExt); err != nil { assert.Fail(t, "Unable to unmarshal bid extension") } - assert.Equal(t, resExt.Prebid.Targeting.DurationRangeSec, durationRange, "Duration range seconds is incorrect") - assert.Equal(t, resExt.Prebid.Targeting.PriceGranularity.Ranges, priceGranRanges, "Price granularity is incorrect") + assert.Equal(t, durationRange, resExt.Prebid.Targeting.DurationRangeSec, "Duration range seconds is incorrect") + assert.Equal(t, priceGranRanges, resExt.Prebid.Targeting.PriceGranularity.Ranges, "Price granularity is incorrect") } func TestCreateBidExtensionTargeting(t *testing.T) { @@ -155,7 +156,7 @@ func TestCreateBidExtensionTargeting(t *testing.T) { require.NotNil(t, ex.lastRequest, "The request never made it into the Exchange.") // assert targeting set to default - expectedRequestExt := `{"prebid":{"cache":{"vastxml":{}},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"mediatypepricegranularity":{},"includebidderkeys":true,"includewinners":true,"includebrandcategory":{"primaryadserver":1,"publisher":"","withcategory":true}}}}` + expectedRequestExt := `{"prebid":{"cache":{"vastxml":{}},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"includebidderkeys":true,"includewinners":true,"includebrandcategory":{"primaryadserver":1,"withcategory":true}}}}` assert.JSONEq(t, expectedRequestExt, string(ex.lastRequest.Ext)) } @@ -184,8 +185,8 @@ func TestVideoEndpointDebugQueryTrue(t *testing.T) { } assert.Len(t, ex.lastRequest.Imp, 11, "Incorrect number of impressions in request") - assert.Equal(t, string(ex.lastRequest.Site.Page), "prebid.com", "Incorrect site page in request") - assert.Equal(t, ex.lastRequest.Site.Content.Series, "TvName", "Incorrect site content series in request") + assert.Equal(t, "prebid.com", string(ex.lastRequest.Site.Page), "Incorrect site page in request") + assert.Equal(t, "TvName", ex.lastRequest.Site.Content.Series, "Incorrect site content series in request") assert.Len(t, resp.AdPods, 5, "Incorrect number of Ad Pods in response") assert.Len(t, resp.AdPods[0].Targeting, 4, "Incorrect Targeting data in response") @@ -194,7 +195,7 @@ func TestVideoEndpointDebugQueryTrue(t *testing.T) { assert.Len(t, resp.AdPods[3].Targeting, 1, "Incorrect Targeting data in response") assert.Len(t, resp.AdPods[4].Targeting, 3, "Incorrect Targeting data in response") - assert.Equal(t, resp.AdPods[4].Targeting[0].HbPbCatDur, "20.00_395_30s", "Incorrect number of Ad Pods in response") + assert.Equal(t, "20.00_395_30s", resp.AdPods[4].Targeting[0].HbPbCatDur, "Incorrect number of Ad Pods in response") } func TestVideoEndpointDebugQueryFalse(t *testing.T) { @@ -222,8 +223,8 @@ func TestVideoEndpointDebugQueryFalse(t *testing.T) { } assert.Len(t, ex.lastRequest.Imp, 11, "Incorrect number of impressions in request") - assert.Equal(t, string(ex.lastRequest.Site.Page), "prebid.com", "Incorrect site page in request") - assert.Equal(t, ex.lastRequest.Site.Content.Series, "TvName", "Incorrect site content series in request") + assert.Equal(t, "prebid.com", string(ex.lastRequest.Site.Page), "Incorrect site page in request") + assert.Equal(t, "TvName", ex.lastRequest.Site.Content.Series, "Incorrect site content series in request") assert.Len(t, resp.AdPods, 5, "Incorrect number of Ad Pods in response") assert.Len(t, resp.AdPods[0].Targeting, 4, "Incorrect Targeting data in response") @@ -232,7 +233,7 @@ func TestVideoEndpointDebugQueryFalse(t *testing.T) { assert.Len(t, resp.AdPods[3].Targeting, 1, "Incorrect Targeting data in response") assert.Len(t, resp.AdPods[4].Targeting, 3, "Incorrect Targeting data in response") - assert.Equal(t, resp.AdPods[4].Targeting[0].HbPbCatDur, "20.00_395_30s", "Incorrect number of Ad Pods in response") + assert.Equal(t, "20.00_395_30s", resp.AdPods[4].Targeting[0].HbPbCatDur, "Incorrect number of Ad Pods in response") } func TestVideoEndpointDebugError(t *testing.T) { @@ -250,7 +251,7 @@ func TestVideoEndpointDebugError(t *testing.T) { t.Fatalf("Cache was not called when it should have been") } - assert.Equal(t, recorder.Code, 500, "Should catch error in request") + assert.Equal(t, 500, recorder.Code, "Should catch error in request") } func TestVideoEndpointDebugNoAdPods(t *testing.T) { @@ -296,7 +297,7 @@ func TestVideoEndpointNoPods(t *testing.T) { errorMessage := recorder.Body.String() - assert.Equal(t, recorder.Code, 500, "Should catch error in request") + assert.Equal(t, 500, recorder.Code, "Should catch error in request") assert.Equal(t, "Critical error while running the video endpoint: request missing required field: PodConfig.DurationRangeSec request missing required field: PodConfig.Pods", errorMessage, "Incorrect request validation message") } @@ -813,15 +814,15 @@ func TestHandleError(t *testing.T) { &errortypes.AccountDisabled{}, }, wantCode: 503, - wantMetricsStatus: metrics.RequestStatusBlacklisted, + wantMetricsStatus: metrics.RequestStatusBlockedApp, }, { description: "Blocked app - return 503 with blocked metrics status", giveErrors: []error{ - &errortypes.BlacklistedApp{}, + &errortypes.BlockedApp{}, }, wantCode: 503, - wantMetricsStatus: metrics.RequestStatusBlacklisted, + wantMetricsStatus: metrics.RequestStatusBlockedApp, }, { description: "Account required error - return 400 with bad input metrics status", @@ -1026,28 +1027,27 @@ func TestHandleErrorDebugLog(t *testing.T) { } func TestCreateImpressionTemplate(t *testing.T) { - imp := openrtb2.Imp{} imp.Video = &openrtb2.Video{} imp.Video.Protocols = []adcom1.MediaCreativeSubtype{1, 2} imp.Video.MIMEs = []string{"video/mp4"} - imp.Video.H = 200 - imp.Video.W = 400 + imp.Video.H = ptrutil.ToPtr[int64](200) + imp.Video.W = ptrutil.ToPtr[int64](400) imp.Video.PlaybackMethod = []adcom1.PlaybackMethod{5, 6} video := openrtb2.Video{} video.Protocols = []adcom1.MediaCreativeSubtype{3, 4} video.MIMEs = []string{"video/flv"} - video.H = 300 - video.W = 0 + video.H = ptrutil.ToPtr[int64](300) + video.W = ptrutil.ToPtr[int64](0) video.PlaybackMethod = []adcom1.PlaybackMethod{7, 8} res := createImpressionTemplate(imp, &video) - assert.Equal(t, res.Video.Protocols, []adcom1.MediaCreativeSubtype{3, 4}, "Incorrect video protocols") - assert.Equal(t, res.Video.MIMEs, []string{"video/flv"}, "Incorrect video MIMEs") - assert.Equal(t, int(res.Video.H), 300, "Incorrect video height") - assert.Equal(t, int(res.Video.W), 0, "Incorrect video width") - assert.Equal(t, res.Video.PlaybackMethod, []adcom1.PlaybackMethod{7, 8}, "Incorrect video playback method") + assert.Equal(t, []adcom1.MediaCreativeSubtype{3, 4}, res.Video.Protocols, "Incorrect video protocols") + assert.Equal(t, []string{"video/flv"}, res.Video.MIMEs, "Incorrect video MIMEs") + assert.Equal(t, ptrutil.ToPtr[int64](300), res.Video.H, "Incorrect video height") + assert.Equal(t, ptrutil.ToPtr[int64](0), res.Video.W, "Incorrect video width") + assert.Equal(t, []adcom1.PlaybackMethod{7, 8}, res.Video.PlaybackMethod, "Incorrect video playback method") } func TestCCPA(t *testing.T) { @@ -1091,14 +1091,11 @@ func TestCCPA(t *testing.T) { if ex.lastRequest == nil { t.Fatalf("%s: The request never made it into the exchange.", test.description) } - extRegs := &openrtb_ext.ExtRegs{} - if err := jsonutil.UnmarshalValid(ex.lastRequest.Regs.Ext, extRegs); err != nil { - t.Fatalf("%s: Failed to unmarshal reg.ext in request to the exchange: %v", test.description, err) - } + if test.expectConsentString { - assert.Len(t, extRegs.USPrivacy, 4, test.description+":consent") + assert.Len(t, ex.lastRequest.Regs.USPrivacy, 4, test.description+":consent") } else if test.expectEmptyConsent { - assert.Empty(t, extRegs.USPrivacy, test.description+":consent") + assert.Empty(t, ex.lastRequest.Regs.USPrivacy, test.description+":consent") } // Validate HTTP Response @@ -1136,8 +1133,8 @@ func TestVideoEndpointAppendBidderNames(t *testing.T) { } assert.Len(t, ex.lastRequest.Imp, 11, "Incorrect number of impressions in request") - assert.Equal(t, string(ex.lastRequest.Site.Page), "prebid.com", "Incorrect site page in request") - assert.Equal(t, ex.lastRequest.Site.Content.Series, "TvName", "Incorrect site content series in request") + assert.Equal(t, "prebid.com", string(ex.lastRequest.Site.Page), "Incorrect site page in request") + assert.Equal(t, "TvName", ex.lastRequest.Site.Content.Series, "Incorrect site content series in request") assert.Len(t, resp.AdPods, 5, "Incorrect number of Ad Pods in response") assert.Len(t, resp.AdPods[0].Targeting, 4, "Incorrect Targeting data in response") @@ -1146,7 +1143,7 @@ func TestVideoEndpointAppendBidderNames(t *testing.T) { assert.Len(t, resp.AdPods[3].Targeting, 1, "Incorrect Targeting data in response") assert.Len(t, resp.AdPods[4].Targeting, 3, "Incorrect Targeting data in response") - assert.Equal(t, resp.AdPods[4].Targeting[0].HbPbCatDur, "20.00_395_30s_appnexus", "Incorrect number of Ad Pods in response") + assert.Equal(t, "20.00_395_30s_appnexus", resp.AdPods[4].Targeting[0].HbPbCatDur, "Incorrect number of Ad Pods in response") } func TestFormatTargetingKey(t *testing.T) { @@ -1163,6 +1160,7 @@ func TestVideoAuctionResponseHeaders(t *testing.T) { testCases := []struct { description string givenTestFile string + givenHeader map[string]string expectedStatus int expectedHeaders func(http.Header) }{ @@ -1174,7 +1172,8 @@ func TestVideoAuctionResponseHeaders(t *testing.T) { h.Set("X-Prebid", "pbs-go/unknown") h.Set("Content-Type", "application/json") }, - }, { + }, + { description: "Failure Response", givenTestFile: "sample-requests/video/video_invalid_sample.json", expectedStatus: 500, @@ -1182,6 +1181,27 @@ func TestVideoAuctionResponseHeaders(t *testing.T) { h.Set("X-Prebid", "pbs-go/unknown") }, }, + { + description: "Success Response with header Observe-Browsing-Topics", + givenTestFile: "sample-requests/video/video_valid_sample.json", + givenHeader: map[string]string{secBrowsingTopics: "anyValue"}, + expectedStatus: 200, + expectedHeaders: func(h http.Header) { + h.Set("X-Prebid", "pbs-go/unknown") + h.Set("Content-Type", "application/json") + h.Set("Observe-Browsing-Topics", "?1") + }, + }, + { + description: "Failure Response with header Observe-Browsing-Topics", + givenTestFile: "sample-requests/video/video_invalid_sample.json", + givenHeader: map[string]string{secBrowsingTopics: "anyValue"}, + expectedStatus: 500, + expectedHeaders: func(h http.Header) { + h.Set("X-Prebid", "pbs-go/unknown") + h.Set("Observe-Browsing-Topics", "?1") + }, + }, } exchange := &mockExchangeVideo{} @@ -1191,6 +1211,9 @@ func TestVideoAuctionResponseHeaders(t *testing.T) { requestBody := readVideoTestFile(t, test.givenTestFile) httpReq := httptest.NewRequest("POST", "/openrtb2/video", strings.NewReader(requestBody)) + for k, v := range test.givenHeader { + httpReq.Header.Add(k, v) + } recorder := httptest.NewRecorder() endpoint.VideoAuctionEndpoint(recorder, httpReq, nil) @@ -1211,7 +1234,7 @@ func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *m deps := &endpointDeps{ fakeUUIDGenerator{}, ex, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockVideoStoredReqFetcher{}, &mockVideoStoredReqFetcher{}, &mockAccountFetcher{data: mockVideoAccountData}, @@ -1250,16 +1273,19 @@ func (m *mockAnalyticsModule) LogCookieSyncObject(cso *analytics.CookieSyncObjec func (m *mockAnalyticsModule) LogSetUIDObject(so *analytics.SetUIDObject) {} -func (m *mockAnalyticsModule) LogAmpObject(ao *analytics.AmpObject, _ privacy.ActivityControl) {} +func (m *mockAnalyticsModule) LogAmpObject(ao *analytics.AmpObject, _ privacy.ActivityControl) { +} func (m *mockAnalyticsModule) LogNotificationEventObject(ne *analytics.NotificationEvent, _ privacy.ActivityControl) { } +func (m *mockAnalyticsModule) Shutdown() {} + func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps { return &endpointDeps{ fakeUUIDGenerator{}, ex, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockVideoStoredReqFetcher{}, &mockVideoStoredReqFetcher{}, &mockAccountFetcher{data: mockVideoAccountData}, @@ -1284,7 +1310,7 @@ func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames) deps := &endpointDeps{ fakeUUIDGenerator{}, ex, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockVideoStoredReqFetcher{}, &mockVideoStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -1311,7 +1337,7 @@ func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps { edep := &endpointDeps{ fakeUUIDGenerator{}, ex, - mockBidderParamValidator{}, + ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}), &mockVideoStoredReqFetcher{}, &mockVideoStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -1472,3 +1498,18 @@ func readVideoTestFile(t *testing.T, filename string) string { return string(getRequestPayload(t, requestData)) } + +func TestVideoRequestValidationFailed(t *testing.T) { + ex := &mockExchangeVideo{} + reqBody := readVideoTestFile(t, "sample-requests/video/video_invalid_sample_negative_tmax.json") + req := httptest.NewRequest("POST", "/openrtb2/video", strings.NewReader(reqBody)) + recorder := httptest.NewRecorder() + + deps := mockDeps(t, ex) + deps.VideoAuctionEndpoint(recorder, req, nil) + + errorMessage := recorder.Body.String() + + assert.Equal(t, 500, recorder.Code, "Should catch error in request") + assert.Equal(t, "Critical error while running the video endpoint: request.tmax must be nonnegative. Got -2", errorMessage, "Incorrect request validation message") +} diff --git a/endpoints/setuid.go b/endpoints/setuid.go index 7af70d002b5..83587610030 100644 --- a/endpoints/setuid.go +++ b/endpoints/setuid.go @@ -12,19 +12,19 @@ import ( "github.com/julienschmidt/httprouter" gpplib "github.com/prebid/go-gpp" gppConstants "github.com/prebid/go-gpp/constants" - accountService "github.com/prebid/prebid-server/account" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy" - gppPrivacy "github.com/prebid/prebid-server/privacy/gpp" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/httputil" - stringutil "github.com/prebid/prebid-server/util/stringutil" + accountService "github.com/prebid/prebid-server/v3/account" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/gdpr" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/privacy" + gppPrivacy "github.com/prebid/prebid-server/v3/privacy/gpp" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/usersync" + "github.com/prebid/prebid-server/v3/util/httputil" + stringutil "github.com/prebid/prebid-server/v3/util/stringutil" ) const ( @@ -126,7 +126,6 @@ func NewSetUIDEndpoint(cfg *config.Configuration, syncersByBidder map[string]use handleBadStatus(w, http.StatusBadRequest, metrics.SetUidBadRequest, err, metricsEngine, &so) return } - w.Write([]byte("Warning: " + err.Error())) } tcf2Cfg := tcf2CfgBuilder(cfg.GDPR.TCF2, account.GDPR) @@ -223,18 +222,14 @@ func extractGDPRInfo(query url.Values) (reqInfo gdpr.RequestInfo, err error) { // parseGDPRFromGPP parses and validates the "gpp_sid" and "gpp" query fields. func parseGDPRFromGPP(query url.Values) (gdpr.RequestInfo, error) { - var gdprSignal gdpr.Signal = gdpr.SignalAmbiguous - var gdprConsent string = "" - var err error - - gdprSignal, err = parseSignalFromGppSidStr(query.Get("gpp_sid")) + gdprSignal, err := parseSignalFromGppSidStr(query.Get("gpp_sid")) if err != nil { return gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, err } - gdprConsent, err = parseConsentFromGppStr(query.Get("gpp")) - if err != nil { - return gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, err + gdprConsent, errs := parseConsentFromGppStr(query.Get("gpp")) + if len(errs) > 0 { + return gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, errs[0] } return gdpr.RequestInfo{ @@ -306,13 +301,13 @@ func parseSignalFromGppSidStr(strSID string) (gdpr.Signal, error) { return gdprSignal, nil } -func parseConsentFromGppStr(gppQueryValue string) (string, error) { +func parseConsentFromGppStr(gppQueryValue string) (string, []error) { var gdprConsent string if len(gppQueryValue) > 0 { - gpp, err := gpplib.Parse(gppQueryValue) - if err != nil { - return "", err + gpp, errs := gpplib.Parse(gppQueryValue) + if len(errs) > 0 { + return "", errs } if i := gppPrivacy.IndexOfSID(gpp, gppConstants.SectionTCFEU2); i >= 0 { @@ -363,7 +358,7 @@ func getResponseFormat(query url.Values, syncer usersync.Syncer) (string, error) formatEmpty := len(format) == 0 || format[0] == "" if !formatProvided || formatEmpty { - switch syncer.DefaultSyncType() { + switch syncer.DefaultResponseFormat() { case usersync.SyncTypeIFrame: return "b", nil case usersync.SyncTypeRedirect: diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index 8b919c3d596..78944da7c42 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -12,18 +12,18 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/analytics" - analyticsBuild "github.com/prebid/prebid-server/analytics/build" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/v3/analytics" + analyticsBuild "github.com/prebid/prebid-server/v3/analytics/build" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/gdpr" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/usersync" "github.com/stretchr/testify/assert" - metricsConf "github.com/prebid/prebid-server/metrics/config" + metricsConf "github.com/prebid/prebid-server/v3/metrics/config" ) func TestSetUIDEndpoint(t *testing.T) { @@ -34,6 +34,7 @@ func TestSetUIDEndpoint(t *testing.T) { gdprAllowsHostCookies bool gdprReturnsError bool gdprMalformed bool + formatOverride string expectedSyncs map[string]string expectedBody string expectedStatusCode int @@ -217,15 +218,25 @@ func TestSetUIDEndpoint(t *testing.T) { }, { uri: "/setuid?bidder=pubmatic&uid=123&gpp_sid=2,4&gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA" + - "gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", + "&gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, gdprAllowsHostCookies: true, existingSyncs: nil, expectedSyncs: map[string]string{"pubmatic": "123"}, expectedStatusCode: http.StatusOK, - expectedBody: "Warning: 'gpp' value will be used over the one found in the deprecated 'gdpr_consent' field.", - expectedHeaders: map[string]string{"Content-Type": "text/plain; charset=utf-8"}, - description: "Sets uid for a bidder allowed by GDPR in GPP, throws warning because GDPR legacy values weren't used", + expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, + description: "GPP value will be used over the one found in the deprecated GDPR consent field for iframe format", + }, + { + uri: "/setuid?f=i&bidder=pubmatic&uid=123&gpp_sid=2,4&gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA" + + "&gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + gdprAllowsHostCookies: true, + existingSyncs: nil, + expectedSyncs: map[string]string{"pubmatic": "123"}, + expectedStatusCode: http.StatusOK, + expectedHeaders: map[string]string{"Content-Type": "image/png", "Content-Length": "86"}, + description: "GPP value will be used over the one found in the deprecated GDPR consent field for redirect format", }, { uri: "/setuid?bidder=pubmatic&uid=123&gdpr=1&gdpr_consent=malformed", @@ -336,6 +347,17 @@ func TestSetUIDEndpoint(t *testing.T) { expectedStatusCode: http.StatusBadRequest, expectedBody: "invalid gpp_sid encoding, must be a csv list of integers", }, + { + uri: "/setuid?bidder=pubmatic&uid=123", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + existingSyncs: nil, + gdprAllowsHostCookies: true, + formatOverride: "i", + expectedSyncs: map[string]string{"pubmatic": "123"}, + expectedStatusCode: http.StatusOK, + expectedHeaders: map[string]string{"Content-Length": "86", "Content-Type": "image/png"}, + description: "Format not provided in URL, but formatOverride is defined", + }, } analytics := analyticsBuild.New(&config.Analytics{}) @@ -343,7 +365,7 @@ func TestSetUIDEndpoint(t *testing.T) { for _, test := range testCases { response := doRequest(makeRequest(test.uri, test.existingSyncs), analytics, metrics, - test.syncersBidderNameToKey, test.gdprAllowsHostCookies, test.gdprReturnsError, test.gdprMalformed, false, 0, nil) + test.syncersBidderNameToKey, test.gdprAllowsHostCookies, test.gdprReturnsError, test.gdprMalformed, false, 0, nil, test.formatOverride) assert.Equal(t, test.expectedStatusCode, response.Code, "Test Case: %s. /setuid returned unexpected error code", test.description) if test.expectedSyncs != nil { @@ -467,7 +489,7 @@ func TestSetUIDPriorityEjection(t *testing.T) { request.AddCookie(httpCookie) // Make Request to /setuid - response := doRequest(request, analytics, &metricsConf.NilMetricsEngine{}, syncersByBidder, true, false, false, false, test.givenMaxCookieSize, test.givenPriorityGroups) + response := doRequest(request, analytics, &metricsConf.NilMetricsEngine{}, syncersByBidder, true, false, false, false, test.givenMaxCookieSize, test.givenPriorityGroups, "") if test.expectedWarning != "" { assert.Equal(t, test.expectedWarning, response.Body.String(), test.description) @@ -553,7 +575,7 @@ func TestParseSignalFromGPPSID(t *testing.T) { func TestParseConsentFromGppStr(t *testing.T) { type testOutput struct { gdprConsent string - err error + err []error } testCases := []struct { desc string @@ -573,7 +595,7 @@ func TestParseConsentFromGppStr(t *testing.T) { inGppQuery: "malformed", expected: testOutput{ gdprConsent: "", - err: errors.New(`error parsing GPP header, base64 decoding: illegal base64 data at input byte 8`), + err: []error{errors.New(`error parsing GPP header, header must have type=3`)}, }, }, { @@ -597,7 +619,7 @@ func TestParseConsentFromGppStr(t *testing.T) { outConsent, outErr := parseConsentFromGppStr(tc.inGppQuery) assert.Equal(t, tc.expected.gdprConsent, outConsent, tc.desc) - assert.Equal(t, tc.expected.err, outErr, tc.desc) + assert.ElementsMatch(t, tc.expected.err, outErr, tc.desc) } } @@ -636,7 +658,7 @@ func TestParseGDPRFromGPP(t *testing.T) { inUri: "/setuid?gpp=malformed", expected: testOutput{ reqInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, - err: errors.New("error parsing GPP header, base64 decoding: illegal base64 data at input byte 8"), + err: errors.New("error parsing GPP header, header must have type=3"), }, }, { @@ -911,7 +933,7 @@ func TestExtractGDPRInfo(t *testing.T) { inUri: "/setuid?gpp=malformed&gpp_sid=2", expected: testOutput{ requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, - err: errors.New("error parsing GPP header, base64 decoding: illegal base64 data at input byte 8"), + err: errors.New("error parsing GPP header, header must have type=3"), }, }, { @@ -1333,7 +1355,7 @@ func TestSetUIDEndpointMetrics(t *testing.T) { for _, v := range test.cookies { addCookie(req, v) } - response := doRequest(req, analyticsEngine, metricsEngine, test.syncersBidderNameToKey, test.gdprAllowsHostCookies, false, false, test.cfgAccountRequired, 0, nil) + response := doRequest(req, analyticsEngine, metricsEngine, test.syncersBidderNameToKey, test.gdprAllowsHostCookies, false, false, test.cfgAccountRequired, 0, nil, "") assert.Equal(t, test.expectedResponseCode, response.Code, test.description) analyticsEngine.AssertExpectations(t) @@ -1349,7 +1371,7 @@ func TestOptedOut(t *testing.T) { syncersBidderNameToKey := map[string]string{"pubmatic": "pubmatic"} analytics := analyticsBuild.New(&config.Analytics{}) metrics := &metricsConf.NilMetricsEngine{} - response := doRequest(request, analytics, metrics, syncersBidderNameToKey, true, false, false, false, 0, nil) + response := doRequest(request, analytics, metrics, syncersBidderNameToKey, true, false, false, false, 0, nil, "") assert.Equal(t, http.StatusUnauthorized, response.Code) } @@ -1445,6 +1467,30 @@ func TestGetResponseFormat(t *testing.T) { expectedFormat: "i", description: "parameter given is empty (by empty item), use default sync type redirect", }, + { + urlValues: url.Values{"f": []string{""}}, + syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeRedirect}, + expectedFormat: "i", + description: "parameter given is empty (by empty item), use default sync type redirect", + }, + { + urlValues: url.Values{"f": []string{}}, + syncer: fakeSyncer{key: "a", formatOverride: "i"}, + expectedFormat: "i", + description: "format not provided, but formatOverride is defined, expect i", + }, + { + urlValues: url.Values{"f": []string{}}, + syncer: fakeSyncer{key: "a", formatOverride: "b"}, + expectedFormat: "b", + description: "format not provided, but formatOverride is defined, expect b", + }, + { + urlValues: url.Values{"f": []string{}}, + syncer: fakeSyncer{key: "a", formatOverride: "b", defaultSyncType: usersync.SyncTypeRedirect}, + expectedFormat: "b", + description: "format not provided, default is defined but formatOverride is defined as well, expect b", + }, } for _, test := range testCases { @@ -1544,7 +1590,7 @@ func makeRequest(uri string, existingSyncs map[string]string) *http.Request { return request } -func doRequest(req *http.Request, analytics analytics.Runner, metrics metrics.MetricsEngine, syncersBidderNameToKey map[string]string, gdprAllowsHostCookies, gdprReturnsError, gdprReturnsMalformedError, cfgAccountRequired bool, maxCookieSize int, priorityGroups [][]string) *httptest.ResponseRecorder { +func doRequest(req *http.Request, analytics analytics.Runner, metrics metrics.MetricsEngine, syncersBidderNameToKey map[string]string, gdprAllowsHostCookies, gdprReturnsError, gdprReturnsMalformedError, cfgAccountRequired bool, maxCookieSize int, priorityGroups [][]string, formatOverride string) *httptest.ResponseRecorder { cfg := config.Configuration{ AccountRequired: cfgAccountRequired, AccountDefaults: config.Account{}, @@ -1575,7 +1621,7 @@ func doRequest(req *http.Request, analytics analytics.Runner, metrics metrics.Me syncersByBidder := make(map[string]usersync.Syncer) for bidderName, syncerKey := range syncersBidderNameToKey { - syncersByBidder[bidderName] = fakeSyncer{key: syncerKey, defaultSyncType: usersync.SyncTypeIFrame} + syncersByBidder[bidderName] = fakeSyncer{key: syncerKey, defaultSyncType: usersync.SyncTypeIFrame, formatOverride: formatOverride} if priorityGroups == nil { cfg.UserSync.PriorityGroups = [][]string{{}} cfg.UserSync.PriorityGroups[0] = append(cfg.UserSync.PriorityGroups[0], bidderName) @@ -1655,25 +1701,33 @@ func (g *fakePermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return false, nil } -func (g *fakePermsSetUID) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) { +func (g *fakePermsSetUID) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) gdpr.AuctionPermissions { return gdpr.AuctionPermissions{ AllowBidRequest: g.personalInfoAllowed, PassGeo: g.personalInfoAllowed, PassID: g.personalInfoAllowed, - }, nil + } } type fakeSyncer struct { key string defaultSyncType usersync.SyncType + formatOverride string } func (s fakeSyncer) Key() string { return s.key } -func (s fakeSyncer) DefaultSyncType() usersync.SyncType { - return s.defaultSyncType +func (s fakeSyncer) DefaultResponseFormat() usersync.SyncType { + switch s.formatOverride { + case "b": + return usersync.SyncTypeIFrame + case "i": + return usersync.SyncTypeRedirect + default: + return s.defaultSyncType + } } func (s fakeSyncer) SupportsType(syncTypes []usersync.SyncType) bool { diff --git a/endpoints/version.go b/endpoints/version.go index 0a837fd8bc3..8eb305c3f5e 100644 --- a/endpoints/version.go +++ b/endpoints/version.go @@ -5,7 +5,7 @@ import ( "net/http" "github.com/golang/glog" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) const versionEndpointValueNotSet = "not-set" diff --git a/errortypes/code.go b/errortypes/code.go index 1de5e648cef..49cb6d4bcf9 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -5,7 +5,7 @@ const ( UnknownErrorCode = 999 TimeoutErrorCode = iota BadInputErrorCode - BlacklistedAppErrorCode + BlockedAppErrorCode BadServerResponseErrorCode FailedToRequestBidsErrorCode BidderTemporarilyDisabledErrorCode @@ -17,6 +17,7 @@ const ( TmaxTimeoutErrorCode FailedToMarshalErrorCode FailedToUnmarshalErrorCode + InvalidImpFirstPartyDataErrorCode ) // Defines numeric codes for well-known warnings. @@ -31,6 +32,9 @@ const ( AdServerTargetingWarningCode BidAdjustmentWarningCode FloorBidRejectionWarningCode + InvalidBidResponseDSAWarningCode + SecCookieDeprecationLenWarningCode + SecBrowsingTopicsWarningCode ) // Coder provides an error or warning code with severity. diff --git a/errortypes/errortypes.go b/errortypes/errortypes.go index d31c4166b06..4e51847b5ea 100644 --- a/errortypes/errortypes.go +++ b/errortypes/errortypes.go @@ -59,23 +59,20 @@ func (err *BadInput) Severity() Severity { return SeverityFatal } -// BlacklistedApp should be used when a request App.ID matches an entry in the BlacklistedApps -// environment variable array -// -// These errors will be written to http.ResponseWriter before canceling execution -type BlacklistedApp struct { +// BlockedApp should be used when a request App.ID matches an entry in the BlockedApp configuration. +type BlockedApp struct { Message string } -func (err *BlacklistedApp) Error() string { +func (err *BlockedApp) Error() string { return err.Message } -func (err *BlacklistedApp) Code() int { - return BlacklistedAppErrorCode +func (err *BlockedApp) Code() int { + return BlockedAppErrorCode } -func (err *BlacklistedApp) Severity() Severity { +func (err *BlockedApp) Severity() Severity { return SeverityFatal } @@ -251,3 +248,44 @@ func (err *FailedToMarshal) Code() int { func (err *FailedToMarshal) Severity() Severity { return SeverityFatal } + +// DebugWarning is a generic non-fatal error used in debug mode. Throughout the codebase, an error can +// only be a warning if it's of the type defined below +type DebugWarning struct { + Message string + WarningCode int +} + +func (err *DebugWarning) Error() string { + return err.Message +} + +func (err *DebugWarning) Code() int { + return err.WarningCode +} + +func (err *DebugWarning) Severity() Severity { + return SeverityWarning +} + +func (err *DebugWarning) Scope() Scope { + return ScopeDebug +} + +// InvalidImpFirstPartyData should be used when the retrieved account config cannot be unmarshaled +// These errors will be written to http.ResponseWriter before canceling execution +type InvalidImpFirstPartyData struct { + Message string +} + +func (err *InvalidImpFirstPartyData) Error() string { + return err.Message +} + +func (err *InvalidImpFirstPartyData) Code() int { + return InvalidImpFirstPartyDataErrorCode +} + +func (err *InvalidImpFirstPartyData) Severity() Severity { + return SeverityFatal +} diff --git a/errortypes/scope.go b/errortypes/scope.go new file mode 100644 index 00000000000..b97284358f5 --- /dev/null +++ b/errortypes/scope.go @@ -0,0 +1,19 @@ +package errortypes + +type Scope int + +const ( + ScopeAny Scope = iota + ScopeDebug +) + +type Scoped interface { + Scope() Scope +} + +func ReadScope(err error) Scope { + if e, ok := err.(Scoped); ok { + return e.Scope() + } + return ScopeAny +} diff --git a/errortypes/scope_test.go b/errortypes/scope_test.go new file mode 100644 index 00000000000..7d90d5585fd --- /dev/null +++ b/errortypes/scope_test.go @@ -0,0 +1,37 @@ +package errortypes + +import ( + "errors" + "testing" +) + +func TestReadScope(t *testing.T) { + tests := []struct { + name string + err error + want Scope + }{ + { + name: "scope-debug", + err: &DebugWarning{Message: "scope is debug"}, + want: ScopeDebug, + }, + { + name: "scope-any", + err: &Warning{Message: "scope is any"}, + want: ScopeAny, + }, + { + name: "default-error", + err: errors.New("default error"), + want: ScopeAny, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ReadScope(tt.err); got != tt.want { + t.Errorf("ReadScope() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 3ffeb6b8589..a82383df7b4 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -1,195 +1,231 @@ package exchange import ( - "github.com/prebid/prebid-server/adapters" - ttx "github.com/prebid/prebid-server/adapters/33across" - "github.com/prebid/prebid-server/adapters/aax" - "github.com/prebid/prebid-server/adapters/aceex" - "github.com/prebid/prebid-server/adapters/acuityads" - "github.com/prebid/prebid-server/adapters/adf" - "github.com/prebid/prebid-server/adapters/adgeneration" - "github.com/prebid/prebid-server/adapters/adhese" - "github.com/prebid/prebid-server/adapters/adkernel" - "github.com/prebid/prebid-server/adapters/adkernelAdn" - "github.com/prebid/prebid-server/adapters/adman" - "github.com/prebid/prebid-server/adapters/admixer" - "github.com/prebid/prebid-server/adapters/adnuntius" - "github.com/prebid/prebid-server/adapters/adocean" - "github.com/prebid/prebid-server/adapters/adoppler" - "github.com/prebid/prebid-server/adapters/adot" - "github.com/prebid/prebid-server/adapters/adpone" - "github.com/prebid/prebid-server/adapters/adprime" - "github.com/prebid/prebid-server/adapters/adquery" - "github.com/prebid/prebid-server/adapters/adrino" - "github.com/prebid/prebid-server/adapters/adsinteractive" - "github.com/prebid/prebid-server/adapters/adtarget" - "github.com/prebid/prebid-server/adapters/adtelligent" - "github.com/prebid/prebid-server/adapters/adtrgtme" - "github.com/prebid/prebid-server/adapters/advangelists" - "github.com/prebid/prebid-server/adapters/adview" - "github.com/prebid/prebid-server/adapters/adxcg" - "github.com/prebid/prebid-server/adapters/adyoulike" - "github.com/prebid/prebid-server/adapters/aidem" - "github.com/prebid/prebid-server/adapters/aja" - "github.com/prebid/prebid-server/adapters/algorix" - "github.com/prebid/prebid-server/adapters/amx" - "github.com/prebid/prebid-server/adapters/apacdex" - "github.com/prebid/prebid-server/adapters/appnexus" - "github.com/prebid/prebid-server/adapters/appush" - "github.com/prebid/prebid-server/adapters/audienceNetwork" - "github.com/prebid/prebid-server/adapters/automatad" - "github.com/prebid/prebid-server/adapters/avocet" - "github.com/prebid/prebid-server/adapters/axis" - "github.com/prebid/prebid-server/adapters/axonix" - "github.com/prebid/prebid-server/adapters/beachfront" - "github.com/prebid/prebid-server/adapters/beintoo" - "github.com/prebid/prebid-server/adapters/bematterfull" - "github.com/prebid/prebid-server/adapters/between" - "github.com/prebid/prebid-server/adapters/beyondmedia" - "github.com/prebid/prebid-server/adapters/bidmachine" - "github.com/prebid/prebid-server/adapters/bidmyadz" - "github.com/prebid/prebid-server/adapters/bidscube" - "github.com/prebid/prebid-server/adapters/bidstack" - "github.com/prebid/prebid-server/adapters/bizzclick" - "github.com/prebid/prebid-server/adapters/bliink" - "github.com/prebid/prebid-server/adapters/blue" - "github.com/prebid/prebid-server/adapters/bluesea" - "github.com/prebid/prebid-server/adapters/bmtm" - "github.com/prebid/prebid-server/adapters/boldwin" - "github.com/prebid/prebid-server/adapters/brave" - cadentaperturemx "github.com/prebid/prebid-server/adapters/cadent_aperture_mx" - "github.com/prebid/prebid-server/adapters/ccx" - "github.com/prebid/prebid-server/adapters/coinzilla" - "github.com/prebid/prebid-server/adapters/colossus" - "github.com/prebid/prebid-server/adapters/compass" - "github.com/prebid/prebid-server/adapters/connectad" - "github.com/prebid/prebid-server/adapters/consumable" - "github.com/prebid/prebid-server/adapters/conversant" - "github.com/prebid/prebid-server/adapters/cpmstar" - "github.com/prebid/prebid-server/adapters/criteo" - "github.com/prebid/prebid-server/adapters/cwire" - "github.com/prebid/prebid-server/adapters/datablocks" - "github.com/prebid/prebid-server/adapters/decenterads" - "github.com/prebid/prebid-server/adapters/deepintent" - "github.com/prebid/prebid-server/adapters/definemedia" - "github.com/prebid/prebid-server/adapters/dianomi" - "github.com/prebid/prebid-server/adapters/dmx" - "github.com/prebid/prebid-server/adapters/dxkulture" - evolution "github.com/prebid/prebid-server/adapters/e_volution" - "github.com/prebid/prebid-server/adapters/edge226" - "github.com/prebid/prebid-server/adapters/emtv" - "github.com/prebid/prebid-server/adapters/eplanning" - "github.com/prebid/prebid-server/adapters/epom" - "github.com/prebid/prebid-server/adapters/flipp" - "github.com/prebid/prebid-server/adapters/freewheelssp" - "github.com/prebid/prebid-server/adapters/frvradn" - "github.com/prebid/prebid-server/adapters/gamma" - "github.com/prebid/prebid-server/adapters/gamoshi" - "github.com/prebid/prebid-server/adapters/globalsun" - "github.com/prebid/prebid-server/adapters/gothamads" - "github.com/prebid/prebid-server/adapters/grid" - "github.com/prebid/prebid-server/adapters/gumgum" - "github.com/prebid/prebid-server/adapters/huaweiads" - "github.com/prebid/prebid-server/adapters/imds" - "github.com/prebid/prebid-server/adapters/impactify" - "github.com/prebid/prebid-server/adapters/improvedigital" - "github.com/prebid/prebid-server/adapters/infytv" - "github.com/prebid/prebid-server/adapters/infytvhb" - "github.com/prebid/prebid-server/adapters/inmobi" - "github.com/prebid/prebid-server/adapters/interactiveoffers" - "github.com/prebid/prebid-server/adapters/invibes" - "github.com/prebid/prebid-server/adapters/iqx" - "github.com/prebid/prebid-server/adapters/iqzone" - "github.com/prebid/prebid-server/adapters/ix" - "github.com/prebid/prebid-server/adapters/jixie" - "github.com/prebid/prebid-server/adapters/kargo" - "github.com/prebid/prebid-server/adapters/kayzen" - "github.com/prebid/prebid-server/adapters/kidoz" - "github.com/prebid/prebid-server/adapters/kiviads" - "github.com/prebid/prebid-server/adapters/krushmedia" - "github.com/prebid/prebid-server/adapters/lemmadigital" - "github.com/prebid/prebid-server/adapters/liftoff" - "github.com/prebid/prebid-server/adapters/limelightDigital" - lmkiviads "github.com/prebid/prebid-server/adapters/lm_kiviads" - "github.com/prebid/prebid-server/adapters/lockerdome" - "github.com/prebid/prebid-server/adapters/logan" - "github.com/prebid/prebid-server/adapters/logicad" - "github.com/prebid/prebid-server/adapters/lunamedia" - mabidder "github.com/prebid/prebid-server/adapters/mabidder" - "github.com/prebid/prebid-server/adapters/madvertise" - "github.com/prebid/prebid-server/adapters/marsmedia" - "github.com/prebid/prebid-server/adapters/medianet" - "github.com/prebid/prebid-server/adapters/mgid" - "github.com/prebid/prebid-server/adapters/mgidX" - "github.com/prebid/prebid-server/adapters/mobfoxpb" - "github.com/prebid/prebid-server/adapters/mobilefuse" - "github.com/prebid/prebid-server/adapters/motorik" - "github.com/prebid/prebid-server/adapters/nextmillennium" - "github.com/prebid/prebid-server/adapters/nobid" - "github.com/prebid/prebid-server/adapters/onetag" - "github.com/prebid/prebid-server/adapters/openweb" - "github.com/prebid/prebid-server/adapters/openx" - "github.com/prebid/prebid-server/adapters/operaads" - "github.com/prebid/prebid-server/adapters/orbidder" - "github.com/prebid/prebid-server/adapters/outbrain" - "github.com/prebid/prebid-server/adapters/ownadx" - "github.com/prebid/prebid-server/adapters/pangle" - "github.com/prebid/prebid-server/adapters/pgamssp" - "github.com/prebid/prebid-server/adapters/pubmatic" - "github.com/prebid/prebid-server/adapters/pubnative" - "github.com/prebid/prebid-server/adapters/pulsepoint" - "github.com/prebid/prebid-server/adapters/pwbid" - "github.com/prebid/prebid-server/adapters/revcontent" - "github.com/prebid/prebid-server/adapters/richaudience" - "github.com/prebid/prebid-server/adapters/rise" - "github.com/prebid/prebid-server/adapters/rtbhouse" - "github.com/prebid/prebid-server/adapters/rubicon" - salunamedia "github.com/prebid/prebid-server/adapters/sa_lunamedia" - "github.com/prebid/prebid-server/adapters/screencore" - "github.com/prebid/prebid-server/adapters/seedingAlliance" - "github.com/prebid/prebid-server/adapters/sharethrough" - "github.com/prebid/prebid-server/adapters/silvermob" - "github.com/prebid/prebid-server/adapters/silverpush" - "github.com/prebid/prebid-server/adapters/smaato" - "github.com/prebid/prebid-server/adapters/smartadserver" - "github.com/prebid/prebid-server/adapters/smarthub" - "github.com/prebid/prebid-server/adapters/smartrtb" - "github.com/prebid/prebid-server/adapters/smartx" - "github.com/prebid/prebid-server/adapters/smartyads" - "github.com/prebid/prebid-server/adapters/smilewanted" - "github.com/prebid/prebid-server/adapters/sonobi" - "github.com/prebid/prebid-server/adapters/sovrn" - "github.com/prebid/prebid-server/adapters/sspBC" - "github.com/prebid/prebid-server/adapters/stroeerCore" - "github.com/prebid/prebid-server/adapters/suntContent" - "github.com/prebid/prebid-server/adapters/taboola" - "github.com/prebid/prebid-server/adapters/tappx" - "github.com/prebid/prebid-server/adapters/teads" - "github.com/prebid/prebid-server/adapters/telaria" - "github.com/prebid/prebid-server/adapters/tpmn" - "github.com/prebid/prebid-server/adapters/trafficgate" - "github.com/prebid/prebid-server/adapters/triplelift" - "github.com/prebid/prebid-server/adapters/triplelift_native" - "github.com/prebid/prebid-server/adapters/ucfunnel" - "github.com/prebid/prebid-server/adapters/undertone" - "github.com/prebid/prebid-server/adapters/unicorn" - "github.com/prebid/prebid-server/adapters/unruly" - "github.com/prebid/prebid-server/adapters/videobyte" - "github.com/prebid/prebid-server/adapters/videoheroes" - "github.com/prebid/prebid-server/adapters/vidoomy" - "github.com/prebid/prebid-server/adapters/visiblemeasures" - "github.com/prebid/prebid-server/adapters/visx" - "github.com/prebid/prebid-server/adapters/vox" - "github.com/prebid/prebid-server/adapters/vrtcal" - "github.com/prebid/prebid-server/adapters/xeworks" - "github.com/prebid/prebid-server/adapters/yahooAds" - "github.com/prebid/prebid-server/adapters/yeahmobi" - "github.com/prebid/prebid-server/adapters/yieldlab" - "github.com/prebid/prebid-server/adapters/yieldmo" - "github.com/prebid/prebid-server/adapters/yieldone" - "github.com/prebid/prebid-server/adapters/zeroclickfraud" - "github.com/prebid/prebid-server/adapters/zeta_global_ssp" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters" + ttx "github.com/prebid/prebid-server/v3/adapters/33across" + "github.com/prebid/prebid-server/v3/adapters/aax" + "github.com/prebid/prebid-server/v3/adapters/aceex" + "github.com/prebid/prebid-server/v3/adapters/acuityads" + "github.com/prebid/prebid-server/v3/adapters/adelement" + "github.com/prebid/prebid-server/v3/adapters/adf" + "github.com/prebid/prebid-server/v3/adapters/adgeneration" + "github.com/prebid/prebid-server/v3/adapters/adhese" + "github.com/prebid/prebid-server/v3/adapters/adkernel" + "github.com/prebid/prebid-server/v3/adapters/adkernelAdn" + "github.com/prebid/prebid-server/v3/adapters/adman" + "github.com/prebid/prebid-server/v3/adapters/admatic" + "github.com/prebid/prebid-server/v3/adapters/admixer" + "github.com/prebid/prebid-server/v3/adapters/adnuntius" + "github.com/prebid/prebid-server/v3/adapters/adocean" + "github.com/prebid/prebid-server/v3/adapters/adoppler" + "github.com/prebid/prebid-server/v3/adapters/adot" + "github.com/prebid/prebid-server/v3/adapters/adpone" + "github.com/prebid/prebid-server/v3/adapters/adprime" + "github.com/prebid/prebid-server/v3/adapters/adquery" + "github.com/prebid/prebid-server/v3/adapters/adrino" + "github.com/prebid/prebid-server/v3/adapters/adsinteractive" + "github.com/prebid/prebid-server/v3/adapters/adtarget" + "github.com/prebid/prebid-server/v3/adapters/adtelligent" + "github.com/prebid/prebid-server/v3/adapters/adtonos" + "github.com/prebid/prebid-server/v3/adapters/adtrgtme" + "github.com/prebid/prebid-server/v3/adapters/advangelists" + "github.com/prebid/prebid-server/v3/adapters/adview" + "github.com/prebid/prebid-server/v3/adapters/adxcg" + "github.com/prebid/prebid-server/v3/adapters/adyoulike" + "github.com/prebid/prebid-server/v3/adapters/aidem" + "github.com/prebid/prebid-server/v3/adapters/aja" + "github.com/prebid/prebid-server/v3/adapters/algorix" + "github.com/prebid/prebid-server/v3/adapters/alkimi" + "github.com/prebid/prebid-server/v3/adapters/amx" + "github.com/prebid/prebid-server/v3/adapters/apacdex" + "github.com/prebid/prebid-server/v3/adapters/appnexus" + "github.com/prebid/prebid-server/v3/adapters/appush" + "github.com/prebid/prebid-server/v3/adapters/aso" + "github.com/prebid/prebid-server/v3/adapters/audienceNetwork" + "github.com/prebid/prebid-server/v3/adapters/automatad" + "github.com/prebid/prebid-server/v3/adapters/avocet" + "github.com/prebid/prebid-server/v3/adapters/axis" + "github.com/prebid/prebid-server/v3/adapters/axonix" + "github.com/prebid/prebid-server/v3/adapters/beachfront" + "github.com/prebid/prebid-server/v3/adapters/beintoo" + "github.com/prebid/prebid-server/v3/adapters/bematterfull" + "github.com/prebid/prebid-server/v3/adapters/between" + "github.com/prebid/prebid-server/v3/adapters/beyondmedia" + "github.com/prebid/prebid-server/v3/adapters/bidmachine" + "github.com/prebid/prebid-server/v3/adapters/bidmatic" + "github.com/prebid/prebid-server/v3/adapters/bidmyadz" + "github.com/prebid/prebid-server/v3/adapters/bidscube" + "github.com/prebid/prebid-server/v3/adapters/bidstack" + "github.com/prebid/prebid-server/v3/adapters/bigoad" + "github.com/prebid/prebid-server/v3/adapters/blasto" + "github.com/prebid/prebid-server/v3/adapters/bliink" + "github.com/prebid/prebid-server/v3/adapters/blue" + "github.com/prebid/prebid-server/v3/adapters/bluesea" + "github.com/prebid/prebid-server/v3/adapters/bmtm" + "github.com/prebid/prebid-server/v3/adapters/boldwin" + "github.com/prebid/prebid-server/v3/adapters/brave" + "github.com/prebid/prebid-server/v3/adapters/bwx" + cadentaperturemx "github.com/prebid/prebid-server/v3/adapters/cadent_aperture_mx" + "github.com/prebid/prebid-server/v3/adapters/ccx" + "github.com/prebid/prebid-server/v3/adapters/cointraffic" + "github.com/prebid/prebid-server/v3/adapters/coinzilla" + "github.com/prebid/prebid-server/v3/adapters/colossus" + "github.com/prebid/prebid-server/v3/adapters/compass" + "github.com/prebid/prebid-server/v3/adapters/concert" + "github.com/prebid/prebid-server/v3/adapters/connectad" + "github.com/prebid/prebid-server/v3/adapters/consumable" + "github.com/prebid/prebid-server/v3/adapters/conversant" + "github.com/prebid/prebid-server/v3/adapters/copper6ssp" + "github.com/prebid/prebid-server/v3/adapters/cpmstar" + "github.com/prebid/prebid-server/v3/adapters/criteo" + "github.com/prebid/prebid-server/v3/adapters/cwire" + "github.com/prebid/prebid-server/v3/adapters/datablocks" + "github.com/prebid/prebid-server/v3/adapters/decenterads" + "github.com/prebid/prebid-server/v3/adapters/deepintent" + "github.com/prebid/prebid-server/v3/adapters/definemedia" + "github.com/prebid/prebid-server/v3/adapters/dianomi" + "github.com/prebid/prebid-server/v3/adapters/displayio" + "github.com/prebid/prebid-server/v3/adapters/dmx" + "github.com/prebid/prebid-server/v3/adapters/driftpixel" + "github.com/prebid/prebid-server/v3/adapters/dxkulture" + evolution "github.com/prebid/prebid-server/v3/adapters/e_volution" + "github.com/prebid/prebid-server/v3/adapters/edge226" + "github.com/prebid/prebid-server/v3/adapters/emtv" + "github.com/prebid/prebid-server/v3/adapters/eplanning" + "github.com/prebid/prebid-server/v3/adapters/epom" + "github.com/prebid/prebid-server/v3/adapters/escalax" + "github.com/prebid/prebid-server/v3/adapters/flipp" + "github.com/prebid/prebid-server/v3/adapters/freewheelssp" + "github.com/prebid/prebid-server/v3/adapters/frvradn" + "github.com/prebid/prebid-server/v3/adapters/gamma" + "github.com/prebid/prebid-server/v3/adapters/gamoshi" + "github.com/prebid/prebid-server/v3/adapters/globalsun" + "github.com/prebid/prebid-server/v3/adapters/gothamads" + "github.com/prebid/prebid-server/v3/adapters/grid" + "github.com/prebid/prebid-server/v3/adapters/gumgum" + "github.com/prebid/prebid-server/v3/adapters/huaweiads" + "github.com/prebid/prebid-server/v3/adapters/imds" + "github.com/prebid/prebid-server/v3/adapters/impactify" + "github.com/prebid/prebid-server/v3/adapters/improvedigital" + "github.com/prebid/prebid-server/v3/adapters/infytv" + "github.com/prebid/prebid-server/v3/adapters/infytvhb" + "github.com/prebid/prebid-server/v3/adapters/inmobi" + "github.com/prebid/prebid-server/v3/adapters/interactiveoffers" + "github.com/prebid/prebid-server/v3/adapters/invibes" + "github.com/prebid/prebid-server/v3/adapters/iqx" + "github.com/prebid/prebid-server/v3/adapters/iqzone" + "github.com/prebid/prebid-server/v3/adapters/ix" + "github.com/prebid/prebid-server/v3/adapters/jixie" + "github.com/prebid/prebid-server/v3/adapters/kargo" + "github.com/prebid/prebid-server/v3/adapters/kayzen" + "github.com/prebid/prebid-server/v3/adapters/kidoz" + "github.com/prebid/prebid-server/v3/adapters/kiviads" + "github.com/prebid/prebid-server/v3/adapters/krushmedia" + "github.com/prebid/prebid-server/v3/adapters/lemmadigital" + "github.com/prebid/prebid-server/v3/adapters/limelightDigital" + lmkiviads "github.com/prebid/prebid-server/v3/adapters/lm_kiviads" + "github.com/prebid/prebid-server/v3/adapters/lockerdome" + "github.com/prebid/prebid-server/v3/adapters/logan" + "github.com/prebid/prebid-server/v3/adapters/logicad" + "github.com/prebid/prebid-server/v3/adapters/loyal" + "github.com/prebid/prebid-server/v3/adapters/lunamedia" + "github.com/prebid/prebid-server/v3/adapters/mabidder" + "github.com/prebid/prebid-server/v3/adapters/madvertise" + "github.com/prebid/prebid-server/v3/adapters/marsmedia" + "github.com/prebid/prebid-server/v3/adapters/mediago" + "github.com/prebid/prebid-server/v3/adapters/medianet" + "github.com/prebid/prebid-server/v3/adapters/melozen" + "github.com/prebid/prebid-server/v3/adapters/metax" + "github.com/prebid/prebid-server/v3/adapters/mgid" + "github.com/prebid/prebid-server/v3/adapters/mgidX" + "github.com/prebid/prebid-server/v3/adapters/minutemedia" + "github.com/prebid/prebid-server/v3/adapters/missena" + "github.com/prebid/prebid-server/v3/adapters/mobfoxpb" + "github.com/prebid/prebid-server/v3/adapters/mobilefuse" + "github.com/prebid/prebid-server/v3/adapters/motorik" + "github.com/prebid/prebid-server/v3/adapters/nativo" + "github.com/prebid/prebid-server/v3/adapters/nextmillennium" + "github.com/prebid/prebid-server/v3/adapters/nobid" + "github.com/prebid/prebid-server/v3/adapters/oms" + "github.com/prebid/prebid-server/v3/adapters/onetag" + "github.com/prebid/prebid-server/v3/adapters/openweb" + "github.com/prebid/prebid-server/v3/adapters/openx" + "github.com/prebid/prebid-server/v3/adapters/operaads" + "github.com/prebid/prebid-server/v3/adapters/oraki" + "github.com/prebid/prebid-server/v3/adapters/orbidder" + "github.com/prebid/prebid-server/v3/adapters/outbrain" + "github.com/prebid/prebid-server/v3/adapters/ownadx" + "github.com/prebid/prebid-server/v3/adapters/pangle" + "github.com/prebid/prebid-server/v3/adapters/pgamssp" + "github.com/prebid/prebid-server/v3/adapters/playdigo" + "github.com/prebid/prebid-server/v3/adapters/pubmatic" + "github.com/prebid/prebid-server/v3/adapters/pubnative" + "github.com/prebid/prebid-server/v3/adapters/pubrise" + "github.com/prebid/prebid-server/v3/adapters/pulsepoint" + "github.com/prebid/prebid-server/v3/adapters/pwbid" + "github.com/prebid/prebid-server/v3/adapters/qt" + "github.com/prebid/prebid-server/v3/adapters/readpeak" + "github.com/prebid/prebid-server/v3/adapters/relevantdigital" + "github.com/prebid/prebid-server/v3/adapters/revcontent" + "github.com/prebid/prebid-server/v3/adapters/richaudience" + "github.com/prebid/prebid-server/v3/adapters/rise" + "github.com/prebid/prebid-server/v3/adapters/roulax" + "github.com/prebid/prebid-server/v3/adapters/rtbhouse" + "github.com/prebid/prebid-server/v3/adapters/rubicon" + salunamedia "github.com/prebid/prebid-server/v3/adapters/sa_lunamedia" + "github.com/prebid/prebid-server/v3/adapters/screencore" + "github.com/prebid/prebid-server/v3/adapters/seedingAlliance" + "github.com/prebid/prebid-server/v3/adapters/sharethrough" + "github.com/prebid/prebid-server/v3/adapters/silvermob" + "github.com/prebid/prebid-server/v3/adapters/silverpush" + "github.com/prebid/prebid-server/v3/adapters/smaato" + "github.com/prebid/prebid-server/v3/adapters/smartadserver" + "github.com/prebid/prebid-server/v3/adapters/smarthub" + "github.com/prebid/prebid-server/v3/adapters/smartrtb" + "github.com/prebid/prebid-server/v3/adapters/smartx" + "github.com/prebid/prebid-server/v3/adapters/smartyads" + "github.com/prebid/prebid-server/v3/adapters/smilewanted" + "github.com/prebid/prebid-server/v3/adapters/smrtconnect" + "github.com/prebid/prebid-server/v3/adapters/sonobi" + "github.com/prebid/prebid-server/v3/adapters/sovrn" + "github.com/prebid/prebid-server/v3/adapters/sovrnXsp" + "github.com/prebid/prebid-server/v3/adapters/sspBC" + "github.com/prebid/prebid-server/v3/adapters/stroeerCore" + "github.com/prebid/prebid-server/v3/adapters/taboola" + "github.com/prebid/prebid-server/v3/adapters/tappx" + "github.com/prebid/prebid-server/v3/adapters/teads" + "github.com/prebid/prebid-server/v3/adapters/telaria" + "github.com/prebid/prebid-server/v3/adapters/theadx" + "github.com/prebid/prebid-server/v3/adapters/thetradedesk" + "github.com/prebid/prebid-server/v3/adapters/tpmn" + "github.com/prebid/prebid-server/v3/adapters/trafficgate" + "github.com/prebid/prebid-server/v3/adapters/triplelift" + "github.com/prebid/prebid-server/v3/adapters/triplelift_native" + "github.com/prebid/prebid-server/v3/adapters/trustedstack" + "github.com/prebid/prebid-server/v3/adapters/ucfunnel" + "github.com/prebid/prebid-server/v3/adapters/undertone" + "github.com/prebid/prebid-server/v3/adapters/unicorn" + "github.com/prebid/prebid-server/v3/adapters/unruly" + "github.com/prebid/prebid-server/v3/adapters/vidazoo" + "github.com/prebid/prebid-server/v3/adapters/videobyte" + "github.com/prebid/prebid-server/v3/adapters/videoheroes" + "github.com/prebid/prebid-server/v3/adapters/vidoomy" + "github.com/prebid/prebid-server/v3/adapters/visiblemeasures" + "github.com/prebid/prebid-server/v3/adapters/visx" + "github.com/prebid/prebid-server/v3/adapters/vox" + "github.com/prebid/prebid-server/v3/adapters/vrtcal" + "github.com/prebid/prebid-server/v3/adapters/vungle" + "github.com/prebid/prebid-server/v3/adapters/xeworks" + "github.com/prebid/prebid-server/v3/adapters/yahooAds" + "github.com/prebid/prebid-server/v3/adapters/yandex" + "github.com/prebid/prebid-server/v3/adapters/yeahmobi" + "github.com/prebid/prebid-server/v3/adapters/yieldlab" + "github.com/prebid/prebid-server/v3/adapters/yieldmo" + "github.com/prebid/prebid-server/v3/adapters/yieldone" + "github.com/prebid/prebid-server/v3/adapters/zeroclickfraud" + "github.com/prebid/prebid-server/v3/adapters/zeta_global_ssp" + "github.com/prebid/prebid-server/v3/adapters/zmaticoo" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // Adapter registration is kept in this separate file for ease of use and to aid @@ -201,12 +237,14 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderAax: aax.Builder, openrtb_ext.BidderAceex: aceex.Builder, openrtb_ext.BidderAcuityAds: acuityads.Builder, + openrtb_ext.BidderAdelement: adelement.Builder, openrtb_ext.BidderAdf: adf.Builder, openrtb_ext.BidderAdgeneration: adgeneration.Builder, openrtb_ext.BidderAdhese: adhese.Builder, openrtb_ext.BidderAdkernel: adkernel.Builder, openrtb_ext.BidderAdkernelAdn: adkernelAdn.Builder, openrtb_ext.BidderAdman: adman.Builder, + openrtb_ext.BidderAdmatic: admatic.Builder, openrtb_ext.BidderAdmixer: admixer.Builder, openrtb_ext.BidderAdnuntius: adnuntius.Builder, openrtb_ext.BidderAdOcean: adocean.Builder, @@ -220,6 +258,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderAdtarget: adtarget.Builder, openrtb_ext.BidderAdtrgtme: adtrgtme.Builder, openrtb_ext.BidderAdtelligent: adtelligent.Builder, + openrtb_ext.BidderAdTonos: adtonos.Builder, openrtb_ext.BidderAdvangelists: advangelists.Builder, openrtb_ext.BidderAdView: adview.Builder, openrtb_ext.BidderAdxcg: adxcg.Builder, @@ -227,10 +266,12 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderAidem: aidem.Builder, openrtb_ext.BidderAJA: aja.Builder, openrtb_ext.BidderAlgorix: algorix.Builder, + openrtb_ext.BidderAlkimi: alkimi.Builder, openrtb_ext.BidderAMX: amx.Builder, openrtb_ext.BidderApacdex: apacdex.Builder, openrtb_ext.BidderAppnexus: appnexus.Builder, openrtb_ext.BidderAppush: appush.Builder, + openrtb_ext.BidderAso: aso.Builder, openrtb_ext.BidderAudienceNetwork: audienceNetwork.Builder, openrtb_ext.BidderAutomatad: automatad.Builder, openrtb_ext.BidderAvocet: avocet.Builder, @@ -242,24 +283,30 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderBetween: between.Builder, openrtb_ext.BidderBeyondMedia: beyondmedia.Builder, openrtb_ext.BidderBidmachine: bidmachine.Builder, + openrtb_ext.BidderBidmatic: bidmatic.Builder, openrtb_ext.BidderBidmyadz: bidmyadz.Builder, openrtb_ext.BidderBidsCube: bidscube.Builder, openrtb_ext.BidderBidstack: bidstack.Builder, - openrtb_ext.BidderBizzclick: bizzclick.Builder, + openrtb_ext.BidderBigoAd: bigoad.Builder, + openrtb_ext.BidderBlasto: blasto.Builder, openrtb_ext.BidderBliink: bliink.Builder, openrtb_ext.BidderBlue: blue.Builder, openrtb_ext.BidderBluesea: bluesea.Builder, openrtb_ext.BidderBmtm: bmtm.Builder, openrtb_ext.BidderBoldwin: boldwin.Builder, openrtb_ext.BidderBrave: brave.Builder, + openrtb_ext.BidderBWX: bwx.Builder, openrtb_ext.BidderCadentApertureMX: cadentaperturemx.Builder, openrtb_ext.BidderCcx: ccx.Builder, + openrtb_ext.BidderCointraffic: cointraffic.Builder, openrtb_ext.BidderCoinzilla: coinzilla.Builder, openrtb_ext.BidderColossus: colossus.Builder, openrtb_ext.BidderCompass: compass.Builder, + openrtb_ext.BidderConcert: concert.Builder, openrtb_ext.BidderConnectAd: connectad.Builder, openrtb_ext.BidderConsumable: consumable.Builder, openrtb_ext.BidderConversant: conversant.Builder, + openrtb_ext.BidderCopper6ssp: copper6ssp.Builder, openrtb_ext.BidderCpmstar: cpmstar.Builder, openrtb_ext.BidderCriteo: criteo.Builder, openrtb_ext.BidderCWire: cwire.Builder, @@ -268,18 +315,19 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderDeepintent: deepintent.Builder, openrtb_ext.BidderDefinemedia: definemedia.Builder, openrtb_ext.BidderDianomi: dianomi.Builder, + openrtb_ext.BidderDisplayio: displayio.Builder, openrtb_ext.BidderEdge226: edge226.Builder, openrtb_ext.BidderDmx: dmx.Builder, openrtb_ext.BidderDXKulture: dxkulture.Builder, + openrtb_ext.BidderDriftPixel: driftpixel.Builder, openrtb_ext.BidderEmtv: emtv.Builder, openrtb_ext.BidderEmxDigital: cadentaperturemx.Builder, openrtb_ext.BidderEPlanning: eplanning.Builder, openrtb_ext.BidderEpom: epom.Builder, - openrtb_ext.BidderEpsilon: conversant.Builder, + openrtb_ext.BidderEscalax: escalax.Builder, openrtb_ext.BidderEVolution: evolution.Builder, openrtb_ext.BidderFlipp: flipp.Builder, openrtb_ext.BidderFreewheelSSP: freewheelssp.Builder, - openrtb_ext.BidderFreewheelSSPOld: freewheelssp.Builder, openrtb_ext.BidderFRVRAdNetwork: frvradn.Builder, openrtb_ext.BidderGamma: gamma.Builder, openrtb_ext.BidderGamoshi: gamoshi.Builder, @@ -307,40 +355,55 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderLmKiviads: lmkiviads.Builder, openrtb_ext.BidderKrushmedia: krushmedia.Builder, openrtb_ext.BidderLemmadigital: lemmadigital.Builder, - openrtb_ext.BidderLiftoff: liftoff.Builder, + openrtb_ext.BidderVungle: vungle.Builder, openrtb_ext.BidderLimelightDigital: limelightDigital.Builder, openrtb_ext.BidderLockerDome: lockerdome.Builder, openrtb_ext.BidderLogan: logan.Builder, openrtb_ext.BidderLogicad: logicad.Builder, + openrtb_ext.BidderLoyal: loyal.Builder, openrtb_ext.BidderLunaMedia: lunamedia.Builder, openrtb_ext.BidderMabidder: mabidder.Builder, openrtb_ext.BidderMadvertise: madvertise.Builder, openrtb_ext.BidderMarsmedia: marsmedia.Builder, openrtb_ext.BidderMediafuse: appnexus.Builder, + openrtb_ext.BidderMediaGo: mediago.Builder, openrtb_ext.BidderMedianet: medianet.Builder, + openrtb_ext.BidderMeloZen: melozen.Builder, + openrtb_ext.BidderMetaX: metax.Builder, openrtb_ext.BidderMgid: mgid.Builder, openrtb_ext.BidderMgidX: mgidX.Builder, + openrtb_ext.BidderMinuteMedia: minutemedia.Builder, + openrtb_ext.BidderMissena: missena.Builder, openrtb_ext.BidderMobfoxpb: mobfoxpb.Builder, openrtb_ext.BidderMobileFuse: mobilefuse.Builder, openrtb_ext.BidderMotorik: motorik.Builder, + openrtb_ext.BidderNativo: nativo.Builder, openrtb_ext.BidderNextMillennium: nextmillennium.Builder, openrtb_ext.BidderNoBid: nobid.Builder, + openrtb_ext.BidderOms: oms.Builder, openrtb_ext.BidderOneTag: onetag.Builder, openrtb_ext.BidderOpenWeb: openweb.Builder, openrtb_ext.BidderOpenx: openx.Builder, openrtb_ext.BidderOperaads: operaads.Builder, + openrtb_ext.BidderOraki: oraki.Builder, openrtb_ext.BidderOrbidder: orbidder.Builder, openrtb_ext.BidderOutbrain: outbrain.Builder, openrtb_ext.BidderOwnAdx: ownadx.Builder, openrtb_ext.BidderPangle: pangle.Builder, openrtb_ext.BidderPGAMSsp: pgamssp.Builder, + openrtb_ext.BidderPlaydigo: playdigo.Builder, openrtb_ext.BidderPubmatic: pubmatic.Builder, openrtb_ext.BidderPubnative: pubnative.Builder, + openrtb_ext.BidderPubrise: pubrise.Builder, openrtb_ext.BidderPulsepoint: pulsepoint.Builder, openrtb_ext.BidderPWBid: pwbid.Builder, + openrtb_ext.BidderQT: qt.Builder, + openrtb_ext.BidderReadpeak: readpeak.Builder, + openrtb_ext.BidderRelevantDigital: relevantdigital.Builder, openrtb_ext.BidderRevcontent: revcontent.Builder, openrtb_ext.BidderRichaudience: richaudience.Builder, openrtb_ext.BidderRise: rise.Builder, + openrtb_ext.BidderRoulax: roulax.Builder, openrtb_ext.BidderRTBHouse: rtbhouse.Builder, openrtb_ext.BidderRubicon: rubicon.Builder, openrtb_ext.BidderSeedingAlliance: seedingAlliance.Builder, @@ -356,25 +419,28 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderSmartx: smartx.Builder, openrtb_ext.BidderSmartyAds: smartyads.Builder, openrtb_ext.BidderSmileWanted: smilewanted.Builder, + openrtb_ext.BidderSmrtconnect: smrtconnect.Builder, openrtb_ext.BidderSonobi: sonobi.Builder, openrtb_ext.BidderSovrn: sovrn.Builder, + openrtb_ext.BidderSovrnXsp: sovrnXsp.Builder, openrtb_ext.BidderSspBC: sspBC.Builder, - openrtb_ext.BidderSuntContent: suntContent.Builder, openrtb_ext.BidderStroeerCore: stroeerCore.Builder, - openrtb_ext.BidderSynacormedia: imds.Builder, openrtb_ext.BidderTaboola: taboola.Builder, openrtb_ext.BidderTappx: tappx.Builder, openrtb_ext.BidderTeads: teads.Builder, openrtb_ext.BidderTelaria: telaria.Builder, + openrtb_ext.BidderTheadx: theadx.Builder, + openrtb_ext.BidderTheTradeDesk: thetradedesk.Builder, openrtb_ext.BidderTpmn: tpmn.Builder, openrtb_ext.BidderTrafficGate: trafficgate.Builder, openrtb_ext.BidderTriplelift: triplelift.Builder, openrtb_ext.BidderTripleliftNative: triplelift_native.Builder, - openrtb_ext.BidderTrustX: grid.Builder, + openrtb_ext.BidderTrustedstack: trustedstack.Builder, openrtb_ext.BidderUcfunnel: ucfunnel.Builder, openrtb_ext.BidderUndertone: undertone.Builder, openrtb_ext.BidderUnicorn: unicorn.Builder, openrtb_ext.BidderUnruly: unruly.Builder, + openrtb_ext.BidderVidazoo: vidazoo.Builder, openrtb_ext.BidderVideoByte: videobyte.Builder, openrtb_ext.BidderVideoHeroes: videoheroes.Builder, openrtb_ext.BidderVidoomy: vidoomy.Builder, @@ -384,13 +450,13 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderVrtcal: vrtcal.Builder, openrtb_ext.BidderXeworks: xeworks.Builder, openrtb_ext.BidderYahooAds: yahooAds.Builder, - openrtb_ext.BidderYahooAdvertising: yahooAds.Builder, - openrtb_ext.BidderYahooSSP: yahooAds.Builder, + openrtb_ext.BidderYandex: yandex.Builder, openrtb_ext.BidderYeahmobi: yeahmobi.Builder, openrtb_ext.BidderYieldlab: yieldlab.Builder, openrtb_ext.BidderYieldmo: yieldmo.Builder, openrtb_ext.BidderYieldone: yieldone.Builder, openrtb_ext.BidderZeroClickFraud: zeroclickfraud.Builder, openrtb_ext.BidderZetaGlobalSsp: zeta_global_ssp.Builder, + openrtb_ext.BidderZmaticoo: zmaticoo.Builder, } } diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index 89d58d06800..e70c32055a0 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -4,10 +4,10 @@ import ( "fmt" "net/http" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func BuildAdapters(client *http.Client, cfg *config.Configuration, infos config.BidderInfos, me metrics.MetricsEngine) (map[openrtb_ext.BidderName]AdaptedBidder, []error) { @@ -120,6 +120,8 @@ func GetDisabledBidderWarningMessages(infos config.BidderInfos) map[string]strin "applogy": `Bidder "applogy" is no longer available in Prebid Server. Please update your configuration.`, "rhythmone": `Bidder "rhythmone" is no longer available in Prebid Server. Please update your configuration.`, "nanointeractive": `Bidder "nanointeractive" is no longer available in Prebid Server. Please update your configuration.`, + "bizzclick": `Bidder "bizzclick" is no longer available in Prebid Server. Please update your configuration. "bizzclick" has been renamed to "blasto".`, + "liftoff": `Bidder "liftoff" is no longer available in Prebid Server. If you're looking to use the Vungle Exchange adapter, please rename it to "vungle" in your configuration.`, } return mergeRemovedAndDisabledBidderWarningMessages(removed, infos) diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go index 08d751cdadf..3f558af5610 100644 --- a/exchange/adapter_util_test.go +++ b/exchange/adapter_util_test.go @@ -5,13 +5,13 @@ import ( "net/http" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/appnexus" - "github.com/prebid/prebid-server/adapters/rubicon" - "github.com/prebid/prebid-server/config" - metrics "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/adapters/appnexus" + "github.com/prebid/prebid-server/v3/adapters/rubicon" + "github.com/prebid/prebid-server/v3/config" + metrics "github.com/prebid/prebid-server/v3/metrics/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/exchange/auction.go b/exchange/auction.go index abbf56ba799..4d5b8e00375 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -11,12 +11,12 @@ import ( "time" uuid "github.com/gofrs/uuid" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/prebid_cache_client" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) const ( @@ -44,9 +44,9 @@ type DebugData struct { func (d *DebugLog) BuildCacheString() { if d.Regexp != nil { - d.Data.Request = fmt.Sprintf(d.Regexp.ReplaceAllString(d.Data.Request, "")) - d.Data.Headers = fmt.Sprintf(d.Regexp.ReplaceAllString(d.Data.Headers, "")) - d.Data.Response = fmt.Sprintf(d.Regexp.ReplaceAllString(d.Data.Response, "")) + d.Data.Request = fmt.Sprint(d.Regexp.ReplaceAllString(d.Data.Request, "")) + d.Data.Headers = fmt.Sprint(d.Regexp.ReplaceAllString(d.Data.Headers, "")) + d.Data.Response = fmt.Sprint(d.Regexp.ReplaceAllString(d.Data.Response, "")) } d.Data.Request = fmt.Sprintf("%s", d.Data.Request) @@ -108,7 +108,7 @@ func (d *DebugLog) PutDebugLogError(cache prebid_cache_client.Client, timeout in func newAuction(seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, numImps int, preferDeals bool) *auction { winningBids := make(map[string]*entities.PbsOrtbBid, numImps) - winningBidsByBidder := make(map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid, numImps) + allBidsByBidder := make(map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid, numImps) for bidderName, seatBid := range seatBids { if seatBid != nil { @@ -118,10 +118,10 @@ func newAuction(seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, nu winningBids[bid.Bid.ImpID] = bid } - if bidMap, ok := winningBidsByBidder[bid.Bid.ImpID]; ok { + if bidMap, ok := allBidsByBidder[bid.Bid.ImpID]; ok { bidMap[bidderName] = append(bidMap[bidderName], bid) } else { - winningBidsByBidder[bid.Bid.ImpID] = map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder[bid.Bid.ImpID] = map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ bidderName: {bid}, } } @@ -130,8 +130,8 @@ func newAuction(seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, nu } return &auction{ - winningBids: winningBids, - winningBidsByBidder: winningBidsByBidder, + winningBids: winningBids, + allBidsByBidder: allBidsByBidder, } } @@ -151,7 +151,7 @@ func isNewWinningBid(bid, wbid *openrtb2.Bid, preferDeals bool) bool { func (a *auction) validateAndUpdateMultiBid(adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, preferDeals bool, accountDefaultBidLimit int) { bidsSnipped := false // sort bids for multibid targeting - for _, topBidsPerBidder := range a.winningBidsByBidder { + for _, topBidsPerBidder := range a.allBidsByBidder { for bidder, topBids := range topBidsPerBidder { sort.Slice(topBids, func(i, j int) bool { return isNewWinningBid(topBids[i].Bid, topBids[j].Bid, preferDeals) @@ -187,7 +187,7 @@ func (a *auction) validateAndUpdateMultiBid(adapterBids map[openrtb_ext.BidderNa func (a *auction) setRoundedPrices(targetingData targetData) { roundedPrices := make(map[*entities.PbsOrtbBid]string, 5*len(a.winningBids)) - for _, topBidsPerImp := range a.winningBidsByBidder { + for _, topBidsPerImp := range a.allBidsByBidder { for _, topBidsPerBidder := range topBidsPerImp { for _, topBid := range topBidsPerBidder { roundedPrices[topBid] = GetPriceBucket(*topBid.Bid, targetingData) @@ -225,7 +225,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, for _, imp := range bidRequest.Imp { expByImp[imp.ID] = imp.Exp } - for impID, topBidsPerImp := range a.winningBidsByBidder { + for impID, topBidsPerImp := range a.allBidsByBidder { for bidderName, topBidsPerBidder := range topBidsPerImp { for _, topBid := range topBidsPerBidder { isOverallWinner := a.winningBids[impID] == topBid @@ -394,8 +394,8 @@ func defTTL(bidType openrtb_ext.BidType, defaultTTLs *config.DefaultTTLs) (ttl i type auction struct { // winningBids is a map from imp.id to the highest overall CPM bid in that imp. winningBids map[string]*entities.PbsOrtbBid - // winningBidsByBidder stores the highest bid on each imp by each bidder. - winningBidsByBidder map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid + // allBidsByBidder is map from ImpID to another map that maps bidderName to all bids from that bidder. + allBidsByBidder map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid // roundedPrices stores the price strings rounded for each bid according to the price granularity. roundedPrices map[*entities.PbsOrtbBid]string // cacheIds stores the UUIDs from Prebid Cache for fetching the full bid JSON. diff --git a/exchange/auction_response.go b/exchange/auction_response.go index 3b85a4472c2..67ab2dc4bb1 100644 --- a/exchange/auction_response.go +++ b/exchange/auction_response.go @@ -1,8 +1,8 @@ package exchange import ( - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // AuctionResponse contains OpenRTB Bid Response object and its extension (un-marshalled) object diff --git a/exchange/auction_test.go b/exchange/auction_test.go index 199b2abba77..188e4d30eaa 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -11,13 +11,13 @@ import ( "strconv" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/prebid_cache_client" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -191,7 +191,7 @@ func loadCacheSpec(filename string) (*cacheSpec, error) { func runCacheSpec(t *testing.T, fileDisplayName string, specData *cacheSpec) { var bid *entities.PbsOrtbBid winningBidsByImp := make(map[string]*entities.PbsOrtbBid) - winningBidsByBidder := make(map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid) + allBidsByBidder := make(map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid) roundedPrices := make(map[*entities.PbsOrtbBid]string) bidCategory := make(map[string]string) @@ -210,15 +210,15 @@ func runCacheSpec(t *testing.T, fileDisplayName string, specData *cacheSpec) { } // Map this bid if it's the highest we've seen from this bidder so far - if bidMap, ok := winningBidsByBidder[bid.Bid.ImpID]; ok { + if bidMap, ok := allBidsByBidder[bid.Bid.ImpID]; ok { bidMap[pbsBid.Bidder] = append(bidMap[pbsBid.Bidder], bid) } else { - winningBidsByBidder[bid.Bid.ImpID] = map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder[bid.Bid.ImpID] = map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ pbsBid.Bidder: {bid}, } } - for _, topBidsPerBidder := range winningBidsByBidder { + for _, topBidsPerBidder := range allBidsByBidder { for _, topBids := range topBidsPerBidder { sort.Slice(topBids, func(i, j int) bool { return isNewWinningBid(topBids[i].Bid, topBids[j].Bid, true) @@ -263,9 +263,9 @@ func runCacheSpec(t *testing.T, fileDisplayName string, specData *cacheSpec) { } testAuction := &auction{ - winningBids: winningBidsByImp, - winningBidsByBidder: winningBidsByBidder, - roundedPrices: roundedPrices, + winningBids: winningBidsByImp, + allBidsByBidder: allBidsByBidder, + roundedPrices: roundedPrices, } evTracking := &eventTracking{ accountID: "TEST_ACC_ID", @@ -405,7 +405,7 @@ func TestNewAuction(t *testing.T) { winningBids: map[string]*entities.PbsOrtbBid{ "imp1": &bid1p230, }, - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp1": { "appnexus": []*entities.PbsOrtbBid{&bid1p123}, "rubicon": []*entities.PbsOrtbBid{&bid1p230}, @@ -433,7 +433,7 @@ func TestNewAuction(t *testing.T) { "imp1": &bid1p230, "imp2": &bid2p144, }, - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp1": { "appnexus": []*entities.PbsOrtbBid{&bid1p230}, "rubicon": []*entities.PbsOrtbBid{&bid1p077}, @@ -462,7 +462,7 @@ func TestNewAuction(t *testing.T) { winningBids: map[string]*entities.PbsOrtbBid{ "imp1": &bid1p123, }, - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp1": { "appnexus": []*entities.PbsOrtbBid{&bid1p123}, "rubicon": []*entities.PbsOrtbBid{&bid1p088d}, @@ -486,7 +486,7 @@ func TestNewAuction(t *testing.T) { winningBids: map[string]*entities.PbsOrtbBid{ "imp1": &bid1p088d, }, - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp1": { "appnexus": []*entities.PbsOrtbBid{&bid1p123}, "rubicon": []*entities.PbsOrtbBid{&bid1p088d}, @@ -510,7 +510,7 @@ func TestNewAuction(t *testing.T) { winningBids: map[string]*entities.PbsOrtbBid{ "imp1": &bid1p166d, }, - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp1": { "appnexus": []*entities.PbsOrtbBid{&bid1p166d}, "rubicon": []*entities.PbsOrtbBid{&bid1p088d}, @@ -537,7 +537,7 @@ func TestNewAuction(t *testing.T) { winningBids: map[string]*entities.PbsOrtbBid{ "imp1": &bid1p166d, }, - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp1": { "appnexus": []*entities.PbsOrtbBid{&bid1p166d}, "rubicon": []*entities.PbsOrtbBid{&bid1p088d}, @@ -563,7 +563,7 @@ func TestNewAuction(t *testing.T) { "imp1": &bid1p166d, "imp2": &bid2p166, }, - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp1": { "appnexus": []*entities.PbsOrtbBid{&bid1p166d, &bid1p077}, "pubmatic": []*entities.PbsOrtbBid{&bid1p088d, &bid1p123}, @@ -645,11 +645,11 @@ func TestValidateAndUpdateMultiBid(t *testing.T) { } type fields struct { - winningBids map[string]*entities.PbsOrtbBid - winningBidsByBidder map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid - roundedPrices map[*entities.PbsOrtbBid]string - cacheIds map[*openrtb2.Bid]string - vastCacheIds map[*openrtb2.Bid]string + winningBids map[string]*entities.PbsOrtbBid + allBidsByBidder map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid + roundedPrices map[*entities.PbsOrtbBid]string + cacheIds map[*openrtb2.Bid]string + vastCacheIds map[*openrtb2.Bid]string } type args struct { adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid @@ -657,8 +657,8 @@ func TestValidateAndUpdateMultiBid(t *testing.T) { accountDefaultBidLimit int } type want struct { - winningBidsByBidder map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid - adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid + allBidsByBidder map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid + adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid } tests := []struct { description string @@ -673,7 +673,7 @@ func TestValidateAndUpdateMultiBid(t *testing.T) { "imp1": &bid1p166d, "imp2": &bid2p166, }, - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp1": { "appnexus": []*entities.PbsOrtbBid{&bid1p001, &bid1p166d, &bid1p077}, "pubmatic": []*entities.PbsOrtbBid{&bid1p088d, &bid1p123}, @@ -697,7 +697,7 @@ func TestValidateAndUpdateMultiBid(t *testing.T) { preferDeals: true, }, want: want{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp1": { "appnexus": []*entities.PbsOrtbBid{&bid1p166d, &bid1p077, &bid1p001}, "pubmatic": []*entities.PbsOrtbBid{&bid1p088d, &bid1p123}, @@ -724,7 +724,7 @@ func TestValidateAndUpdateMultiBid(t *testing.T) { "imp1": &bid1p166d, "imp2": &bid2p166, }, - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp1": { "appnexus": []*entities.PbsOrtbBid{&bid1p001, &bid1p166d, &bid1p077}, "pubmatic": []*entities.PbsOrtbBid{&bid1p088d, &bid1p123}, @@ -748,7 +748,7 @@ func TestValidateAndUpdateMultiBid(t *testing.T) { preferDeals: true, }, want: want{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp1": { "appnexus": []*entities.PbsOrtbBid{&bid1p166d, &bid1p077, &bid1p001}, "pubmatic": []*entities.PbsOrtbBid{&bid1p088d, &bid1p123}, @@ -775,7 +775,7 @@ func TestValidateAndUpdateMultiBid(t *testing.T) { "imp1": &bid1p166d, "imp2": &bid2p166, }, - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp1": { "appnexus": []*entities.PbsOrtbBid{&bid1p001, &bid1p166d, &bid1p077}, "pubmatic": []*entities.PbsOrtbBid{&bid1p088d, &bid1p123}, @@ -799,7 +799,7 @@ func TestValidateAndUpdateMultiBid(t *testing.T) { preferDeals: true, }, want: want{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp1": { "appnexus": []*entities.PbsOrtbBid{&bid1p166d, &bid1p077}, "pubmatic": []*entities.PbsOrtbBid{&bid1p088d, &bid1p123}, @@ -823,14 +823,14 @@ func TestValidateAndUpdateMultiBid(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { a := &auction{ - winningBids: tt.fields.winningBids, - winningBidsByBidder: tt.fields.winningBidsByBidder, - roundedPrices: tt.fields.roundedPrices, - cacheIds: tt.fields.cacheIds, - vastCacheIds: tt.fields.vastCacheIds, + winningBids: tt.fields.winningBids, + allBidsByBidder: tt.fields.allBidsByBidder, + roundedPrices: tt.fields.roundedPrices, + cacheIds: tt.fields.cacheIds, + vastCacheIds: tt.fields.vastCacheIds, } a.validateAndUpdateMultiBid(tt.args.adapterBids, tt.args.preferDeals, tt.args.accountDefaultBidLimit) - assert.Equal(t, tt.want.winningBidsByBidder, tt.fields.winningBidsByBidder, tt.description) + assert.Equal(t, tt.want.allBidsByBidder, tt.fields.allBidsByBidder, tt.description) assert.Equal(t, tt.want.adapterBids, tt.args.adapterBids, tt.description) }) } diff --git a/exchange/bidder.go b/exchange/bidder.go index 677743801f3..232da470fba 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -13,27 +13,28 @@ import ( "net/http/httptrace" "regexp" "strings" + "sync" "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/bidadjustment" - "github.com/prebid/prebid-server/config/util" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/version" - - "github.com/prebid/openrtb/v19/adcom1" - nativeRequests "github.com/prebid/openrtb/v19/native1/request" - nativeResponse "github.com/prebid/openrtb/v19/native1/response" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/bidadjustment" + "github.com/prebid/prebid-server/v3/config/util" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/experiment/adscert" + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/version" + + "github.com/prebid/openrtb/v20/adcom1" + nativeRequests "github.com/prebid/openrtb/v20/native1/request" + nativeResponse "github.com/prebid/openrtb/v20/native1/response" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" "golang.org/x/net/context/ctxhttp" ) @@ -70,16 +71,19 @@ type bidRequestOptions struct { bidAdjustments map[string]float64 tmaxAdjustments *TmaxAdjustmentsPreprocessed bidderRequestStartTime time.Time + responseDebugAllowed bool } type extraBidderRespInfo struct { respProcessingStartTime time.Time + seatNonBidBuilder SeatNonBidBuilder } type extraAuctionResponseInfo struct { fledge *openrtb_ext.Fledge bidsFound bool bidderResponseStartTime time.Time + seatNonBidBuilder SeatNonBidBuilder } const ImpIdReqBody = "Stored bid response for impression id: " @@ -94,7 +98,7 @@ const ( // The name refers to the "Adapter" architecture pattern, and should not be confused with a Prebid "Adapter" // (which is being phased out and replaced by Bidder for OpenRTB auctions) func AdaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me metrics.MetricsEngine, name openrtb_ext.BidderName, debugInfo *config.DebugInfo, endpointCompression string) AdaptedBidder { - return &bidderAdapter{ + return &BidderAdapter{ Bidder: bidder, BidderName: name, Client: client, @@ -115,7 +119,7 @@ func parseDebugInfo(info *config.DebugInfo) bool { return info.Allow } -type bidderAdapter struct { +type BidderAdapter struct { Bidder adapters.Bidder BidderName openrtb_ext.BidderName Client *http.Client @@ -130,8 +134,10 @@ type bidderAdapterConfig struct { EndpointCompression string } -func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, extraBidderRespInfo, []error) { - reject := hookExecutor.ExecuteBidderRequestStage(bidderRequest.BidRequest, string(bidderRequest.BidderName)) +func (bidder *BidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, extraBidderRespInfo, []error) { + request := openrtb_ext.RequestWrapper{BidRequest: bidderRequest.BidRequest} + reject := hookExecutor.ExecuteBidderRequestStage(&request, string(bidderRequest.BidderName)) + seatNonBidBuilder := SeatNonBidBuilder{} if reject != nil { return nil, extraBidderRespInfo{}, []error{reject} } @@ -143,6 +149,10 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde extraRespInfo extraBidderRespInfo ) + // rebuild request after modules execution + request.RebuildRequest() + bidderRequest.BidRequest = request.BidRequest + //check if real request exists for this bidder or it only has stored responses dataLen := 0 if len(bidderRequest.BidRequest.Imp) > 0 { @@ -327,10 +337,6 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde if err == nil { // Conversion rate found, using it for conversion for i := 0; i < len(bidResponse.Bids); i++ { - if bidResponse.Bids[i].BidMeta == nil { - bidResponse.Bids[i].BidMeta = &openrtb_ext.ExtBidPrebidMeta{} - } - bidResponse.Bids[i].BidMeta.AdapterCode = bidderRequest.BidderName.String() bidderName := bidderRequest.BidderName if bidResponse.Bids[i].Seat != "" { @@ -384,6 +390,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde DealPriority: bidResponse.Bids[i].DealPriority, OriginalBidCPM: originalBidCpm, OriginalBidCur: bidResponse.Currency, + AdapterCode: bidderRequest.BidderCoreName, }) seatBidMap[bidderName].Currency = currencyAfterAdjustments } @@ -394,19 +401,23 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde } } else { errs = append(errs, httpInfo.err) + nonBidReason := httpInfoToNonBidReason(httpInfo) + seatNonBidBuilder.rejectImps(httpInfo.request.ImpIDs, nonBidReason, string(bidderRequest.BidderName)) } } + seatBids := make([]*entities.PbsOrtbSeatBid, 0, len(seatBidMap)) for _, seatBid := range seatBidMap { seatBids = append(seatBids, seatBid) } + extraRespInfo.seatNonBidBuilder = seatNonBidBuilder return seatBids, extraRespInfo, errs } func addNativeTypes(bid *openrtb2.Bid, request *openrtb2.BidRequest) (*nativeResponse.Response, []error) { var errs []error - var nativeMarkup *nativeResponse.Response + var nativeMarkup nativeResponse.Response if err := jsonutil.UnmarshalValid(json.RawMessage(bid.AdM), &nativeMarkup); err != nil || len(nativeMarkup.Assets) == 0 { // Some bidders are returning non-IAB compliant native markup. In this case Prebid server will not be able to add types. E.g Facebook return nil, errs @@ -429,7 +440,7 @@ func addNativeTypes(bid *openrtb2.Bid, request *openrtb2.BidRequest) (*nativeRes } } - return nativeMarkup, errs + return &nativeMarkup, errs } func setAssetTypes(asset nativeResponse.Asset, nativePayload nativeRequests.Request) error { @@ -515,21 +526,19 @@ func makeExt(httpInfo *httpCallInfo) *openrtb_ext.ExtHttpCall { // doRequest makes a request, handles the response, and returns the data needed by the // Bidder interface. -func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.RequestData, bidderRequestStartTime time.Time, tmaxAdjustments *TmaxAdjustmentsPreprocessed) *httpCallInfo { +func (bidder *BidderAdapter) doRequest(ctx context.Context, req *adapters.RequestData, bidderRequestStartTime time.Time, tmaxAdjustments *TmaxAdjustmentsPreprocessed) *httpCallInfo { return bidder.doRequestImpl(ctx, req, glog.Warningf, bidderRequestStartTime, tmaxAdjustments) } -func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.RequestData, logger util.LogMsg, bidderRequestStartTime time.Time, tmaxAdjustments *TmaxAdjustmentsPreprocessed) *httpCallInfo { - var requestBody []byte - - switch strings.ToUpper(bidder.config.EndpointCompression) { - case Gzip: - requestBody = compressToGZIP(req.Body) - req.Headers.Set("Content-Encoding", "gzip") - default: - requestBody = req.Body +func (bidder *BidderAdapter) doRequestImpl(ctx context.Context, req *adapters.RequestData, logger util.LogMsg, bidderRequestStartTime time.Time, tmaxAdjustments *TmaxAdjustmentsPreprocessed) *httpCallInfo { + requestBody, err := getRequestBody(req, bidder.config.EndpointCompression) + if err != nil { + return &httpCallInfo{ + request: req, + err: err, + } } - httpReq, err := http.NewRequest(req.Method, req.Uri, bytes.NewBuffer(requestBody)) + httpReq, err := http.NewRequest(req.Method, req.Uri, requestBody) if err != nil { return &httpCallInfo{ request: req, @@ -608,7 +617,7 @@ func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.Re } } -func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.TimeoutBidder, req *adapters.RequestData, logger util.LogMsg) { +func (bidder *BidderAdapter) doTimeoutNotification(timeoutBidder adapters.TimeoutBidder, req *adapters.RequestData, logger util.LogMsg) { ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel() toReq, errL := timeoutBidder.MakeTimeoutNotification(req) @@ -658,7 +667,7 @@ type httpCallInfo struct { // This function adds an httptrace.ClientTrace object to the context so, if connection with the bidder // endpoint is established, we can keep track of whether the connection was newly created, reused, and // the time from the connection request, to the connection creation. -func (bidder *bidderAdapter) addClientTrace(ctx context.Context) context.Context { +func (bidder *BidderAdapter) addClientTrace(ctx context.Context) context.Context { var connStart, dnsStart, tlsStart time.Time trace := &httptrace.ClientTrace{ @@ -668,7 +677,7 @@ func (bidder *bidderAdapter) addClientTrace(ctx context.Context) context.Context }, // GotConn is called after a successful connection is obtained GotConn: func(info httptrace.GotConnInfo) { - connWaitTime := time.Now().Sub(connStart) + connWaitTime := time.Since(connStart) bidder.me.RecordAdapterConnections(bidder.BidderName, info.Reused, connWaitTime) }, @@ -678,7 +687,7 @@ func (bidder *bidderAdapter) addClientTrace(ctx context.Context) context.Context }, // DNSDone is called when a DNS lookup ends. DNSDone: func(info httptrace.DNSDoneInfo) { - dnsLookupTime := time.Now().Sub(dnsStart) + dnsLookupTime := time.Since(dnsStart) bidder.me.RecordDNSTime(dnsLookupTime) }, @@ -688,7 +697,7 @@ func (bidder *bidderAdapter) addClientTrace(ctx context.Context) context.Context }, TLSHandshakeDone: func(tls.ConnectionState, error) { - tlsHandshakeTime := time.Now().Sub(tlsStart) + tlsHandshakeTime := time.Since(tlsStart) bidder.me.RecordTLSHandshakeTime(tlsHandshakeTime) }, @@ -715,14 +724,6 @@ func prepareStoredResponse(impId string, bidResp json.RawMessage) *httpCallInfo return respData } -func compressToGZIP(requestBody []byte) []byte { - var b bytes.Buffer - w := gzip.NewWriter(&b) - w.Write([]byte(requestBody)) - w.Close() - return b.Bytes() -} - func getBidTypeForAdjustments(bidType openrtb_ext.BidType, impID string, imp []openrtb2.Imp) string { if bidType == openrtb_ext.BidTypeVideo { for _, imp := range imp { @@ -750,3 +751,37 @@ func hasShorterDurationThanTmax(ctx bidderTmaxContext, tmaxAdjustments TmaxAdjus } return false } + +func getRequestBody(req *adapters.RequestData, endpointCompression string) (*bytes.Buffer, error) { + switch strings.ToUpper(endpointCompression) { + case Gzip: + // Compress to GZIP + b := bytes.NewBuffer(make([]byte, 0, len(req.Body))) + + w := gzipWriterPool.Get().(*gzip.Writer) + defer gzipWriterPool.Put(w) + + w.Reset(b) + _, err := w.Write(req.Body) + if err != nil { + return nil, err + } + err = w.Close() + if err != nil { + return nil, err + } + + // Set Header + req.Headers.Set("Content-Encoding", "gzip") + + return b, nil + default: + return bytes.NewBuffer(req.Body), nil + } +} + +var gzipWriterPool = sync.Pool{ + New: func() interface{} { + return gzip.NewWriter(nil) + }, +} diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index b7203e33b17..dbb167e053d 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -2,37 +2,43 @@ package exchange import ( "bytes" + "compress/gzip" "context" "crypto/tls" "encoding/json" "errors" "fmt" "io" + "net" "net/http" "net/http/httptest" "net/http/httptrace" + "net/url" + "os" "sort" "strings" + "syscall" "testing" "time" "github.com/golang/glog" - "github.com/prebid/openrtb/v19/adcom1" - nativeRequests "github.com/prebid/openrtb/v19/native1/request" - nativeResponse "github.com/prebid/openrtb/v19/native1/response" - "github.com/prebid/openrtb/v19/openrtb2" - "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/exchange/entities" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/metrics" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/version" + "github.com/prebid/openrtb/v20/adcom1" + nativeRequests "github.com/prebid/openrtb/v20/native1/request" + nativeResponse "github.com/prebid/openrtb/v20/native1/response" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/experiment/adscert" + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/metrics" + metricsConfig "github.com/prebid/prebid-server/v3/metrics/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" + "github.com/prebid/prebid-server/v3/version" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -550,7 +556,7 @@ func TestBidderTimeout(t *testing.T) { server := httptest.NewServer(handler) defer server.Close() - bidder := &bidderAdapter{ + bidder := &BidderAdapter{ Bidder: &mixedMultiBidder{}, BidderName: openrtb_ext.BidderAppnexus, Client: server.Client(), @@ -572,7 +578,7 @@ func TestBidderTimeout(t *testing.T) { // TestInvalidRequest makes sure that bidderAdapter.doRequest returns errors on bad requests. func TestInvalidRequest(t *testing.T) { server := httptest.NewServer(mockHandler(200, "getBody", "postBody")) - bidder := &bidderAdapter{ + bidder := &BidderAdapter{ Bidder: &mixedMultiBidder{}, Client: server.Client(), } @@ -593,7 +599,7 @@ func TestConnectionClose(t *testing.T) { }) server = httptest.NewServer(handler) - bidder := &bidderAdapter{ + bidder := &BidderAdapter{ Bidder: &mixedMultiBidder{}, Client: server.Client(), BidderName: openrtb_ext.BidderAppnexus, @@ -1063,7 +1069,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { seatBid := seatBids[0] // Verify: - assert.Equal(t, false, (seatBid == nil && tc.expectedBidsCount != 0), tc.description) + assert.Falsef(t, seatBid == nil && tc.expectedBidsCount != 0, tc.description) assert.Equal(t, tc.expectedBidsCount, uint(len(seatBid.Bids)), tc.description) assert.ElementsMatch(t, tc.expectedBadCurrencyErrors, errs, tc.description) assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero()) @@ -1568,6 +1574,46 @@ func TestMobileNativeTypes(t *testing.T) { } } +func TestAddNativeTypes(t *testing.T) { + testCases := []struct { + description string + bidderRequest *openrtb2.BidRequest + bid *openrtb2.Bid + expectedResponse *nativeResponse.Response + expectedErrors []error + }{ + { + description: "Null in bid.Adm in response", + bidderRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + { + ID: "some-imp-id", + Native: &openrtb2.Native{ + Request: "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}}]}", + }, + }, + }, + App: &openrtb2.App{}, + }, + bid: &openrtb2.Bid{ + ImpID: "some-imp-id", + AdM: "null", + Price: 10, + }, + expectedResponse: nil, + expectedErrors: nil, + }, + } + + for _, tt := range testCases { + t.Run(tt.description, func(t *testing.T) { + resp, errs := addNativeTypes(tt.bid, tt.bidderRequest) + assert.Equal(t, tt.expectedResponse, resp, "response") + assert.Equal(t, tt.expectedErrors, errs, "errors") + }) + } +} + func TestRequestBidsStoredBidResponses(t *testing.T) { respBody := "{\"bid\":false}" respStatus := 200 @@ -1839,7 +1885,7 @@ func TestSetAssetTypes(t *testing.T) { }{ { respAsset: nativeResponse.Asset{ - ID: openrtb2.Int64Ptr(1), + ID: ptrutil.ToPtr[int64](1), Img: &nativeResponse.Image{ URL: "http://some-url", }, @@ -1865,7 +1911,7 @@ func TestSetAssetTypes(t *testing.T) { }, { respAsset: nativeResponse.Asset{ - ID: openrtb2.Int64Ptr(2), + ID: ptrutil.ToPtr[int64](2), Data: &nativeResponse.Data{ Label: "some label", }, @@ -1891,7 +1937,7 @@ func TestSetAssetTypes(t *testing.T) { }, { respAsset: nativeResponse.Asset{ - ID: openrtb2.Int64Ptr(1), + ID: ptrutil.ToPtr[int64](1), Img: &nativeResponse.Image{ URL: "http://some-url", }, @@ -1911,7 +1957,7 @@ func TestSetAssetTypes(t *testing.T) { }, { respAsset: nativeResponse.Asset{ - ID: openrtb2.Int64Ptr(2), + ID: ptrutil.ToPtr[int64](2), Data: &nativeResponse.Data{ Label: "some label", }, @@ -1931,7 +1977,7 @@ func TestSetAssetTypes(t *testing.T) { }, { respAsset: nativeResponse.Asset{ - ID: openrtb2.Int64Ptr(1), + ID: ptrutil.ToPtr[int64](1), Img: &nativeResponse.Image{ URL: "http://some-url", }, @@ -2103,7 +2149,7 @@ func TestCallRecordDNSTime(t *testing.T) { // Instantiate the bidder that will send the request. We'll make sure to use an // http.Client that runs our mock RoundTripper so DNSDone(httptrace.DNSDoneInfo{}) // gets called - bidder := &bidderAdapter{ + bidder := &BidderAdapter{ Bidder: &mixedMultiBidder{}, Client: &http.Client{Transport: DNSDoneTripper{}}, me: metricsMock, @@ -2127,7 +2173,7 @@ func TestCallRecordTLSHandshakeTime(t *testing.T) { // Instantiate the bidder that will send the request. We'll make sure to use an // http.Client that runs our mock RoundTripper so DNSDone(httptrace.DNSDoneInfo{}) // gets called - bidder := &bidderAdapter{ + bidder := &BidderAdapter{ Bidder: &mixedMultiBidder{}, Client: &http.Client{Transport: TLSHandshakeTripper{}}, me: metricsMock, @@ -2155,7 +2201,7 @@ func TestTimeoutNotificationOff(t *testing.T) { Headers: http.Header{}, }, } - bidder := &bidderAdapter{ + bidder := &BidderAdapter{ Bidder: bidderImpl, Client: server.Client(), config: bidderAdapterConfig{Debug: config.Debug{}}, @@ -2189,7 +2235,7 @@ func TestTimeoutNotificationOn(t *testing.T) { // Wrap with BidderInfo to mimic exchange.go flow. bidderWrappedWithInfo := wrapWithBidderInfo(bidder) - bidderAdapter := &bidderAdapter{ + bidderAdapter := &BidderAdapter{ Bidder: bidderWrappedWithInfo, Client: server.Client(), config: bidderAdapterConfig{ @@ -2372,10 +2418,7 @@ type goodMultiHTTPCallsBidder struct { func (bidder *goodMultiHTTPCallsBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { bidder.bidRequest = request response := make([]*adapters.RequestData, len(bidder.httpRequest)) - - for i, r := range bidder.httpRequest { - response[i] = r - } + copy(response, bidder.httpRequest) return response, nil } @@ -2480,7 +2523,6 @@ func TestExtraBid(t *testing.T) { DealPriority: 5, BidType: openrtb_ext.BidTypeVideo, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm", Currency: "USD", @@ -2492,7 +2534,6 @@ func TestExtraBid(t *testing.T) { DealPriority: 4, BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "USD", @@ -2590,7 +2631,6 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) { DealPriority: 5, BidType: openrtb_ext.BidTypeVideo, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm-allowed", Currency: "USD", @@ -2602,7 +2642,6 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) { DealPriority: 4, BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "USD", @@ -2700,7 +2739,6 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { BidType: openrtb_ext.BidTypeVideo, OriginalBidCPM: 7, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "PUBMATIC"}, }}, Seat: "groupm", Currency: "USD", @@ -2716,7 +2754,6 @@ func TestExtraBidWithBidAdjustments(t *testing.T) { BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", OriginalBidCPM: 3, - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "PUBMATIC"}, }}, Seat: "PUBMATIC", Currency: "USD", @@ -2815,7 +2852,6 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) { BidType: openrtb_ext.BidTypeVideo, OriginalBidCPM: 7, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm", Currency: "USD", @@ -2831,7 +2867,6 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) { BidType: openrtb_ext.BidTypeBanner, OriginalBidCur: "USD", OriginalBidCPM: 3, - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "USD", @@ -2929,7 +2964,6 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) { BidType: openrtb_ext.BidTypeVideo, OriginalBidCPM: 7, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: "groupm", Currency: "INR", @@ -2945,7 +2979,6 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) { BidType: openrtb_ext.BidTypeBanner, OriginalBidCPM: 3, OriginalBidCur: "USD", - BidMeta: &openrtb_ext.ExtBidPrebidMeta{AdapterCode: string(openrtb_ext.BidderPubmatic)}, }}, Seat: string(openrtb_ext.BidderPubmatic), Currency: "INR", @@ -3067,6 +3100,148 @@ func TestGetBidType(t *testing.T) { } } +func TestSeatNonBid(t *testing.T) { + type args struct { + BidRequest *openrtb2.BidRequest + Seat string + SeatRequests []*adapters.RequestData + BidderResponse func() (*http.Response, error) + client *http.Client + } + type expect struct { + seatBids []*entities.PbsOrtbSeatBid + seatNonBids SeatNonBidBuilder + errors []error + } + testCases := []struct { + name string + args args + expect expect + }{ + { + name: "NBR_101_timeout_for_context_deadline_exceeded", + args: args{ + Seat: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ID: "1234"}}, + }, + SeatRequests: []*adapters.RequestData{{ImpIDs: []string{"1234"}}}, + BidderResponse: func() (*http.Response, error) { return nil, context.DeadlineExceeded }, + client: &http.Client{Timeout: time.Nanosecond}, // for timeout + }, + expect: expect{ + seatNonBids: SeatNonBidBuilder{ + "pubmatic": {{ + ImpId: "1234", + StatusCode: int(ErrorTimeout), + }}, + }, + errors: []error{&errortypes.Timeout{Message: context.DeadlineExceeded.Error()}}, + seatBids: []*entities.PbsOrtbSeatBid{{Bids: []*entities.PbsOrtbBid{}, Currency: "USD", Seat: "pubmatic", HttpCalls: []*openrtb_ext.ExtHttpCall{}}}, + }, + }, { + name: "NBR_103_Bidder_Unreachable_Connection_Refused", + args: args{ + Seat: "appnexus", + SeatRequests: []*adapters.RequestData{{ImpIDs: []string{"1234", "4567"}}}, + BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1234"}, {ID: "4567"}}}, + BidderResponse: func() (*http.Response, error) { + return nil, &net.OpError{Err: os.NewSyscallError(syscall.ECONNREFUSED.Error(), syscall.ECONNREFUSED)} + }, + }, + expect: expect{ + seatNonBids: SeatNonBidBuilder{ + "appnexus": { + {ImpId: "1234", StatusCode: int(ErrorBidderUnreachable)}, + {ImpId: "4567", StatusCode: int(ErrorBidderUnreachable)}, + }, + }, + seatBids: []*entities.PbsOrtbSeatBid{{Bids: []*entities.PbsOrtbBid{}, Currency: "USD", Seat: "appnexus", HttpCalls: []*openrtb_ext.ExtHttpCall{}}}, + errors: []error{&url.Error{Op: "Get", URL: "", Err: &net.OpError{Err: os.NewSyscallError(syscall.ECONNREFUSED.Error(), syscall.ECONNREFUSED)}}}, + }, + }, { + name: "no_impids_populated_in_request_data", + args: args{ + SeatRequests: []*adapters.RequestData{{ + ImpIDs: nil, // no imp ids + }}, + BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1234"}}}, + BidderResponse: func() (*http.Response, error) { + return nil, errors.New("some_error") + }, + }, + expect: expect{ + seatNonBids: SeatNonBidBuilder{}, + seatBids: []*entities.PbsOrtbSeatBid{{Bids: []*entities.PbsOrtbBid{}, Currency: "USD", HttpCalls: []*openrtb_ext.ExtHttpCall{}}}, + errors: []error{&url.Error{Op: "Get", URL: "", Err: errors.New("some_error")}}, + }, + }, + } + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + mockBidder := &mockBidder{} + mockBidder.On("MakeRequests", mock.Anything, mock.Anything).Return(test.args.SeatRequests, []error(nil)) + mockMetricsEngine := &metrics.MetricsEngineMock{} + mockMetricsEngine.On("RecordOverheadTime", mock.Anything, mock.Anything).Return(nil) + mockMetricsEngine.On("RecordBidderServerResponseTime", mock.Anything).Return(nil) + roundTrip := &mockRoundTripper{} + roundTrip.On("RoundTrip", mock.Anything).Return(test.args.BidderResponse()) + client := &http.Client{ + Transport: roundTrip, + Timeout: 0, + } + if test.args.client != nil { + client.Timeout = test.args.client.Timeout + } + bidder := AdaptBidder(mockBidder, client, &config.Configuration{}, mockMetricsEngine, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, test.args.Seat) + + ctx := context.Background() + if client.Timeout > 0 { + ctxTimeout, cancel := context.WithTimeout(ctx, client.Timeout) + ctx = ctxTimeout + defer cancel() + } + seatBids, responseExtra, errors := bidder.requestBid(ctx, BidderRequest{ + BidRequest: test.args.BidRequest, + BidderName: openrtb_ext.BidderName(test.args.Seat), + }, nil, &adapters.ExtraRequestInfo{}, &MockSigner{}, bidRequestOptions{}, openrtb_ext.ExtAlternateBidderCodes{}, hookexecution.EmptyHookExecutor{}, nil) + assert.Equal(t, test.expect.seatBids, seatBids) + assert.Equal(t, test.expect.seatNonBids, responseExtra.seatNonBidBuilder) + assert.Equal(t, test.expect.errors, errors) + for _, nonBids := range responseExtra.seatNonBidBuilder { + for _, nonBid := range nonBids { + for _, seatBid := range seatBids { + for _, bid := range seatBid.Bids { + // ensure non bids are not present in seat bids + if nonBid.ImpId == bid.Bid.ImpID { + assert.Fail(t, "imp id [%s] present in both seat bid and non seat bid", nonBid.ImpId) + } + } + } + } + } + }) + } +} + +type mockRoundTripper struct { + mock.Mock +} + +func (rt *mockRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) { + args := rt.Called(request) + var response *http.Response + if args.Get(0) != nil { + response = args.Get(0).(*http.Response) + } + var err error + if args.Get(1) != nil { + err = args.Get(1).(error) + } + + return response, err +} + type mockBidderTmaxCtx struct { startTime, deadline, now time.Time ok bool @@ -3206,7 +3381,7 @@ func TestDoRequestImplWithTmax(t *testing.T) { Body: []byte(`{"id":"this-id","app":{"publisher":{"id":"pub-id"}}}`), } - bidderAdapter := bidderAdapter{ + bidderAdapter := BidderAdapter{ me: &metricsConfig.NilMetricsEngine{}, Client: server.Client(), } @@ -3281,7 +3456,7 @@ func TestDoRequestImplWithTmaxTimeout(t *testing.T) { metricsMock.On("RecordOverheadTime", metrics.PreBidder, mock.Anything).Once() metricsMock.On("RecordTMaxTimeout").Once() - bidderAdapter := bidderAdapter{ + bidderAdapter := BidderAdapter{ me: metricsMock, Client: server.Client(), } @@ -3317,3 +3492,77 @@ func TestDoRequestImplWithTmaxTimeout(t *testing.T) { test.assertFn(httpCallInfo.err) } } + +func TestGetRequestBody(t *testing.T) { + tests := []struct { + name string + endpointCompression string + givenReqBody []byte + }{ + { + name: "No-Compression", + endpointCompression: "", + givenReqBody: []byte("test body"), + }, + { + name: "GZIP-Compression", + endpointCompression: "GZIP", + givenReqBody: []byte("test body"), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + req := &adapters.RequestData{Body: test.givenReqBody, Headers: http.Header{}} + requestBody, err := getRequestBody(req, test.endpointCompression) + assert.NoError(t, err) + + if test.endpointCompression == "GZIP" { + assert.Equal(t, "gzip", req.Headers.Get("Content-Encoding")) + + decompressedReqBody, err := decompressGzip(requestBody.Bytes()) + assert.NoError(t, err) + assert.Equal(t, test.givenReqBody, decompressedReqBody) + } else { + assert.Equal(t, test.givenReqBody, requestBody.Bytes()) + } + }) + } +} + +func decompressGzip(input []byte) ([]byte, error) { + r, err := gzip.NewReader(bytes.NewReader(input)) + if err != nil { + return nil, err + } + defer r.Close() + + decompressed, err := io.ReadAll(r) + if err != nil { + return nil, err + } + + return decompressed, nil +} + +func BenchmarkCompressToGZIPOptimized(b *testing.B) { + // Setup the mock server + respBody := "{\"bid\":false}" + respStatus := 200 + server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody)) + defer server.Close() + + // Prepare the request data + req := &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte("{\"key\":\"val\"}"), + Headers: http.Header{}, + } + + // Run the benchmark + b.ResetTimer() + for i := 0; i < b.N; i++ { + getRequestBody(req, "GZIP") + } +} diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go index 9b5771a3497..d028008132d 100644 --- a/exchange/bidder_validate_bids.go +++ b/exchange/bidder_validate_bids.go @@ -6,13 +6,13 @@ import ( "fmt" "strings" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/experiment/adscert" + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/openrtb_ext" goCurrency "golang.org/x/text/currency" ) @@ -34,7 +34,7 @@ type validatedBidder struct { func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, extraBidderRespInfo, []error) { seatBids, extraBidderRespInfo, errs := v.bidder.requestBid(ctx, bidderRequest, conversions, reqInfo, adsCertSigner, bidRequestOptions, alternateBidderCodes, hookExecutor, ruleToAdjustments) for _, seatBid := range seatBids { - if validationErrors := removeInvalidBids(bidderRequest.BidRequest, seatBid); len(validationErrors) > 0 { + if validationErrors := removeInvalidBids(bidderRequest.BidRequest, seatBid, bidRequestOptions.responseDebugAllowed); len(validationErrors) > 0 { errs = append(errs, validationErrors...) } } @@ -42,7 +42,7 @@ func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRe } // validateBids will run some validation checks on the returned bids and excise any invalid bids -func removeInvalidBids(request *openrtb2.BidRequest, seatBid *entities.PbsOrtbSeatBid) []error { +func removeInvalidBids(request *openrtb2.BidRequest, seatBid *entities.PbsOrtbSeatBid, debug bool) []error { // Exit early if there is nothing to do. if seatBid == nil || len(seatBid.Bids) == 0 { return nil @@ -57,10 +57,10 @@ func removeInvalidBids(request *openrtb2.BidRequest, seatBid *entities.PbsOrtbSe errs := make([]error, 0, len(seatBid.Bids)) validBids := make([]*entities.PbsOrtbBid, 0, len(seatBid.Bids)) for _, bid := range seatBid.Bids { - if ok, berr := validateBid(bid); ok { + if ok, err := validateBid(bid, debug); ok { validBids = append(validBids, bid) - } else { - errs = append(errs, berr) + } else if err != nil { + errs = append(errs, err) } } seatBid.Bids = validBids @@ -104,7 +104,7 @@ func validateCurrency(requestAllowedCurrencies []string, bidCurrency string) err } // validateBid will run the supplied bid through validation checks and return true if it passes, false otherwise. -func validateBid(bid *entities.PbsOrtbBid) (bool, error) { +func validateBid(bid *entities.PbsOrtbBid, debug bool) (bool, error) { if bid.Bid == nil { return false, errors.New("Empty bid object submitted.") } @@ -116,10 +116,16 @@ func validateBid(bid *entities.PbsOrtbBid) (bool, error) { return false, fmt.Errorf("Bid \"%s\" missing required field 'impid'", bid.Bid.ID) } if bid.Bid.Price < 0.0 { - return false, fmt.Errorf("Bid \"%s\" does not contain a positive (or zero if there is a deal) 'price'", bid.Bid.ID) + if debug { + return false, fmt.Errorf("Bid \"%s\" does not contain a positive (or zero if there is a deal) 'price'", bid.Bid.ID) + } + return false, nil } if bid.Bid.Price == 0.0 && bid.Bid.DealID == "" { - return false, fmt.Errorf("Bid \"%s\" does not contain positive 'price' which is required since there is no deal set for this bid", bid.Bid.ID) + if debug { + return false, fmt.Errorf("Bid \"%s\" does not contain positive 'price' which is required since there is no deal set for this bid", bid.Bid.ID) + } + return false, nil } if bid.Bid.CrID == "" { return false, fmt.Errorf("Bid \"%s\" missing creative ID", bid.Bid.ID) diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go index ed6173b64ad..e14efec4023 100644 --- a/exchange/bidder_validate_bids_test.go +++ b/exchange/bidder_validate_bids_test.go @@ -4,13 +4,13 @@ import ( "context" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/experiment/adscert" + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -130,10 +130,11 @@ func TestAllBadBids(t *testing.T) { } bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0} bidReqOptions := bidRequestOptions{ - accountDebugAllowed: true, - headerDebugAllowed: false, - addCallSignHeader: false, - bidAdjustments: bidAdjustments, + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + responseDebugAllowed: true, } seatBids, _, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) assert.Len(t, seatBids, 1) @@ -211,15 +212,16 @@ func TestMixedBids(t *testing.T) { } bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0} bidReqOptions := bidRequestOptions{ - accountDebugAllowed: true, - headerDebugAllowed: false, - addCallSignHeader: false, - bidAdjustments: bidAdjustments, + accountDebugAllowed: true, + headerDebugAllowed: false, + addCallSignHeader: false, + bidAdjustments: bidAdjustments, + responseDebugAllowed: false, } seatBids, _, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) assert.Len(t, seatBids, 1) assert.Len(t, seatBids[0].Bids, 3) - assert.Len(t, errs, 5) + assert.Len(t, errs, 2) } func TestCurrencyBids(t *testing.T) { diff --git a/exchange/entities/entities.go b/exchange/entities/entities.go index 1220da5c812..808125f1c1c 100644 --- a/exchange/entities/entities.go +++ b/exchange/entities/entities.go @@ -1,8 +1,8 @@ package entities import ( - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // PbsOrtbSeatBid is a SeatBid returned by an AdaptedBidder. @@ -50,4 +50,5 @@ type PbsOrtbBid struct { OriginalBidCPM float64 OriginalBidCur string TargetBidderCode string + AdapterCode openrtb_ext.BidderName } diff --git a/exchange/events.go b/exchange/events.go index fb535f6f70e..d52db53aac6 100644 --- a/exchange/events.go +++ b/exchange/events.go @@ -3,14 +3,14 @@ package exchange import ( "time" - "github.com/prebid/prebid-server/exchange/entities" + "github.com/prebid/prebid-server/v3/exchange/entities" jsonpatch "gopkg.in/evanphx/json-patch.v4" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/endpoints/events" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/analytics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/endpoints/events" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) // eventTracking has configuration fields needed for adding event tracking to an auction response diff --git a/exchange/events_test.go b/exchange/events_test.go index 24dedf1a6f1..3f35ef517de 100644 --- a/exchange/events_test.go +++ b/exchange/events_test.go @@ -3,9 +3,9 @@ package exchange import ( "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/exchange/exchange.go b/exchange/exchange.go index 6ebde7dca9d..9ab91ee9ea3 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -14,35 +14,37 @@ import ( "strings" "time" - "github.com/prebid/prebid-server/privacy" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adservertargeting" - "github.com/prebid/prebid-server/bidadjustment" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/firstpartydata" - "github.com/prebid/prebid-server/floors" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_responses" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/maputil" + "github.com/prebid/prebid-server/v3/ortb" + "github.com/prebid/prebid-server/v3/privacy" + + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/adservertargeting" + "github.com/prebid/prebid-server/v3/bidadjustment" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/dsa" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/experiment/adscert" + "github.com/prebid/prebid-server/v3/firstpartydata" + "github.com/prebid/prebid-server/v3/floors" + "github.com/prebid/prebid-server/v3/gdpr" + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/prebid_cache_client" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/stored_responses" + "github.com/prebid/prebid-server/v3/usersync" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/maputil" "github.com/buger/jsonparser" "github.com/gofrs/uuid" "github.com/golang/glog" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/openrtb/v19/openrtb3" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/openrtb/v20/openrtb3" ) type extCacheInstructions struct { @@ -81,7 +83,8 @@ type exchange struct { bidValidationEnforcement config.Validations requestSplitter requestSplitter macroReplacer macros.Replacer - floor config.PriceFloors + priceFloorEnabled bool + priceFloorFetcher floors.FloorFetcher } // Container to pass out response ext data from the GetAllBids goroutines back into the main thread @@ -92,6 +95,8 @@ type seatResponseExtra struct { // 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 + // NonBid contains non bid reason information + NonBid *openrtb_ext.NonBid } type bidResponseWrapper struct { @@ -100,10 +105,11 @@ type bidResponseWrapper struct { bidder openrtb_ext.BidderName adapter openrtb_ext.BidderName bidderResponseStartTime time.Time + seatNonBidBuilder SeatNonBidBuilder } type BidIDGenerator interface { - New() (string, error) + New(bidder string) (string, error) Enabled() bool } @@ -115,7 +121,7 @@ func (big *bidIDGenerator) Enabled() bool { return big.enabled } -func (big *bidIDGenerator) New() (string, error) { +func (big *bidIDGenerator) New(bidder string) (string, error) { rawUuid, err := uuid.NewV4() return rawUuid.String(), err } @@ -130,7 +136,7 @@ func (randomDeduplicateBidBooleanGenerator) Generate() bool { return rand.Intn(100) < 50 } -func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gdprPermsBuilder gdpr.PermissionsBuilder, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher, adsCertSigner adscert.Signer, macroReplacer macros.Replacer) Exchange { +func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, requestValidator ortb.RequestValidator, syncersByBidder map[string]usersync.Syncer, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gdprPermsBuilder gdpr.PermissionsBuilder, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher, adsCertSigner adscert.Signer, macroReplacer macros.Replacer, priceFloorFetcher floors.FloorFetcher) Exchange { bidderToSyncerKey := map[string]string{} for bidder, syncer := range syncersByBidder { bidderToSyncerKey[bidder] = syncer.Key() @@ -153,6 +159,7 @@ func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid gdprPermsBuilder: gdprPermsBuilder, hostSChainNode: cfg.HostSChainNode, bidderInfo: infos, + requestValidator: requestValidator, } return &exchange{ @@ -175,7 +182,8 @@ func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid bidValidationEnforcement: cfg.Validations, requestSplitter: requestSplitter, macroReplacer: macroReplacer, - floor: cfg.PriceFloors, + priceFloorEnabled: cfg.PriceFloors.Enabled, + priceFloorFetcher: priceFloorFetcher, } } @@ -233,7 +241,6 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog return nil, nil } - var floorErrs []error err := r.HookExecutor.ExecuteProcessedAuctionStage(r.BidRequestWrapper) if err != nil { return nil, err @@ -266,10 +273,11 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog } // Get currency rates conversions for the auction - conversions := e.getAuctionCurrencyRates(requestExtPrebid.CurrencyConversions) + conversions := currency.GetAuctionCurrencyRates(e.currencyConverter, requestExtPrebid.CurrencyConversions) - if e.floor.Enabled { - floorErrs = floors.EnrichWithPriceFloors(r.BidRequestWrapper, r.Account, conversions) + var floorErrs []error + if e.priceFloorEnabled { + floorErrs = floors.EnrichWithPriceFloors(r.BidRequestWrapper, r.Account, conversions, e.priceFloorFetcher) } responseDebugAllow, accountDebugAllow, debugLog := getDebugInfo(r.BidRequestWrapper.Test, requestExtPrebid, r.Account.DebugAllow, debugLog) @@ -310,6 +318,19 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog // Make our best guess if GDPR applies gdprDefaultValue := e.parseGDPRDefaultValue(r.BidRequestWrapper) + gdprSignal, err := getGDPR(r.BidRequestWrapper) + if err != nil { + return nil, err + } + channelEnabled := r.TCF2Config.ChannelEnabled(channelTypeMap[r.LegacyLabels.RType]) + gdprEnforced := enforceGDPR(gdprSignal, gdprDefaultValue, channelEnabled) + dsaWriter := dsa.Writer{ + Config: r.Account.Privacy.DSA, + GDPRInScope: gdprEnforced, + } + if err := dsaWriter.Write(r.BidRequestWrapper); err != nil { + return nil, err + } // rebuild/resync the request in the request wrapper. if err := r.BidRequestWrapper.RebuildRequest(); err != nil { @@ -321,7 +342,12 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog Prebid: *requestExtPrebid, SChain: requestExt.GetSChain(), } - bidderRequests, privacyLabels, errs := e.requestSplitter.cleanOpenRTBRequests(ctx, *r, requestExtLegacy, gdprDefaultValue, bidAdjustmentFactors) + bidderRequests, privacyLabels, errs := e.requestSplitter.cleanOpenRTBRequests(ctx, *r, requestExtLegacy, gdprSignal, gdprEnforced, bidAdjustmentFactors) + for _, err := range errs { + if errortypes.ReadCode(err) == errortypes.InvalidImpFirstPartyDataErrorCode { + return nil, err + } + } errs = append(errs, floorErrs...) mergedBidAdj, err := bidadjustment.Merge(r.BidRequestWrapper, r.Account.BidAdjustments) @@ -350,7 +376,8 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog fledge *openrtb_ext.Fledge anyBidsReturned bool // List of bidders we have requests for. - liveAdapters []openrtb_ext.BidderName + liveAdapters []openrtb_ext.BidderName + seatNonBidBuilder SeatNonBidBuilder = SeatNonBidBuilder{} ) if len(r.StoredAuctionResponses) > 0 { @@ -372,21 +399,23 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog alternateBidderCodes = *r.Account.AlternateBidderCodes } var extraRespInfo extraAuctionResponseInfo - adapterBids, adapterExtra, extraRespInfo = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExtLegacy.Prebid.Experiment, r.HookExecutor, r.StartTime, bidAdjustmentRules, r.TmaxAdjustments) + adapterBids, adapterExtra, extraRespInfo = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExtLegacy.Prebid.Experiment, r.HookExecutor, r.StartTime, bidAdjustmentRules, r.TmaxAdjustments, responseDebugAllow) fledge = extraRespInfo.fledge anyBidsReturned = extraRespInfo.bidsFound r.BidderResponseStartTime = extraRespInfo.bidderResponseStartTime + if extraRespInfo.seatNonBidBuilder != nil { + seatNonBidBuilder = extraRespInfo.seatNonBidBuilder + } } var ( auc *auction cacheErrs []error bidResponseExt *openrtb_ext.ExtBidResponse - seatNonBids = nonBids{} ) if anyBidsReturned { - if e.floor.Enabled { + if e.priceFloorEnabled { var rejectedBids []*entities.PbsOrtbSeatBid var enforceErrs []error @@ -396,6 +425,11 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog errs = append(errs, &errortypes.Warning{ Message: fmt.Sprintf("%s bid id %s rejected - bid price %.4f %s is less than bid floor %.4f %s for imp %s", rejectedBid.Seat, rejectedBid.Bids[0].Bid.ID, rejectedBid.Bids[0].Bid.Price, rejectedBid.Currency, rejectedBid.Bids[0].BidFloors.FloorValue, rejectedBid.Bids[0].BidFloors.FloorCurrency, rejectedBid.Bids[0].Bid.ImpID), WarningCode: errortypes.FloorBidRejectionWarningCode}) + rejectionReason := ResponseRejectedBelowFloor + if rejectedBid.Bids[0].Bid.DealID != "" { + rejectionReason = ResponseRejectedBelowDealFloor + } + seatNonBidBuilder.rejectBid(rejectedBid.Bids[0], int(rejectionReason), rejectedBid.Seat) } } @@ -403,7 +437,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, *requestExtPrebid.Targeting, adapterBids, e.categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBids) + bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, *requestExtPrebid.Targeting, adapterBids, e.categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBidBuilder) if err != nil { return nil, fmt.Errorf("Error in category mapping : %s", err.Error()) } @@ -413,10 +447,11 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog } if e.bidIDGenerator.Enabled() { - for _, seatBid := range adapterBids { - for _, pbsBid := range seatBid.Bids { - pbsBid.GeneratedBidID, err = e.bidIDGenerator.New() - if err != nil { + for bidder, seatBid := range adapterBids { + for i := range seatBid.Bids { + if bidID, err := e.bidIDGenerator.New(bidder.String()); err == nil { + seatBid.Bids[i].GeneratedBidID = bidID + } else { errs = append(errs, errors.New("Error generating bid.ext.prebid.bidid")) } } @@ -456,7 +491,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog errs = append(errs, cacheErrs...) } - targData.setTargeting(auc, r.BidRequestWrapper.BidRequest.App != nil, bidCategory, r.Account.TruncateTargetAttribute, multiBidMap) + if targData.includeWinners || targData.includeBidderKeys || targData.includeFormat { + targData.setTargeting(auc, r.BidRequestWrapper.BidRequest.App != nil, bidCategory, r.Account.TruncateTargetAttribute, multiBidMap) + } } bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, *r, responseDebugAllow, requestExtPrebid.Passthrough, fledge, errs) } else { @@ -482,6 +519,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog } for _, warning := range r.Warnings { + if errortypes.ReadScope(warning) == errortypes.ScopeDebug && !responseDebugAllow { + continue + } generalWarning := openrtb_ext.ExtBidderMessage{ Code: errortypes.ReadCode(warning), Message: warning.Error(), @@ -492,14 +532,14 @@ 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.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, r.ImpExtInfoMap, r.PubID, errs) + bidResponse := e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequestWrapper, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, r.ImpExtInfoMap, r.PubID, errs, &seatNonBidBuilder) 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) + bidResponseExt = setSeatNonBid(bidResponseExt, seatNonBidBuilder) return &AuctionResponse{ BidResponse: bidResponse, @@ -572,18 +612,22 @@ func applyDealSupport(bidRequest *openrtb2.BidRequest, auc *auction, bidCategory errs := []error{} impDealMap := getDealTiers(bidRequest) - for impID, topBidsPerImp := range auc.winningBidsByBidder { + for impID, topBidsPerImp := range auc.allBidsByBidder { impDeal := impDealMap[impID] for bidder, topBidsPerBidder := range topBidsPerImp { - maxBid := bidsToUpdate(multiBid, bidder.String()) + bidderNormalized, bidderFound := openrtb_ext.NormalizeBidderName(bidder.String()) + if !bidderFound { + bidderNormalized = bidder + } + maxBid := bidsToUpdate(multiBid, bidderNormalized.String()) for i, topBid := range topBidsPerBidder { if i == maxBid { break } if topBid.DealPriority > 0 { - if validateDealTier(impDeal[bidder]) { - updateHbPbCatDur(topBid, impDeal[bidder], bidCategory) + if validateDealTier(impDeal[bidderNormalized]) { + updateHbPbCatDur(topBid, impDeal[bidderNormalized], bidCategory) } else { errs = append(errs, fmt.Errorf("dealTier configuration invalid for bidder '%s', imp ID '%s'", string(bidder), impID)) } @@ -666,7 +710,8 @@ func (e *exchange) getAllBids( hookExecutor hookexecution.StageExecutor, pbsRequestStartTime time.Time, bidAdjustmentRules map[string][]openrtb_ext.Adjustment, - tmaxAdjustments *TmaxAdjustmentsPreprocessed) ( + tmaxAdjustments *TmaxAdjustmentsPreprocessed, + responseDebugAllowed bool) ( map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, map[openrtb_ext.BidderName]*seatResponseExtra, extraAuctionResponseInfo) { @@ -674,7 +719,7 @@ func (e *exchange) getAllBids( adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, len(bidderRequests)) adapterExtra := make(map[openrtb_ext.BidderName]*seatResponseExtra, len(bidderRequests)) chBids := make(chan *bidResponseWrapper, len(bidderRequests)) - extraRespInfo := extraAuctionResponseInfo{} + extraRespInfo := extraAuctionResponseInfo{seatNonBidBuilder: SeatNonBidBuilder{}} e.me.RecordOverheadTime(metrics.MakeBidderRequests, time.Since(pbsRequestStartTime)) @@ -706,6 +751,7 @@ func (e *exchange) getAllBids( bidAdjustments: bidAdjustments, tmaxAdjustments: tmaxAdjustments, bidderRequestStartTime: start, + responseDebugAllowed: responseDebugAllowed, } seatBids, extraBidderRespInfo, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, conversions, &reqInfo, e.adsCertSigner, bidReqOptions, alternateBidderCodes, hookExecutor, bidAdjustmentRules) brw.bidderResponseStartTime = extraBidderRespInfo.respProcessingStartTime @@ -713,6 +759,7 @@ func (e *exchange) getAllBids( // Add in time reporting elapsed := time.Since(start) brw.adapterSeatBids = seatBids + brw.seatNonBidBuilder = extraBidderRespInfo.seatNonBidBuilder // Structure to record extra tracking data generated during bidding ae := new(seatResponseExtra) ae.ResponseTimeMillis = int(elapsed / time.Millisecond) @@ -757,6 +804,7 @@ func (e *exchange) getAllBids( } else { adapterBids[bidderName] = seatBid } + extraRespInfo.bidsFound = true } // collect fledgeAuctionConfigs separately from bids, as empty seatBids may be discarded extraRespInfo.fledge = collectFledgeFromSeatBid(extraRespInfo.fledge, bidderName, brw.adapter, seatBid) @@ -765,9 +813,9 @@ func (e *exchange) getAllBids( //but we need to add all bidders data to adapterExtra to have metrics and other metadata adapterExtra[brw.bidder] = brw.adapterExtra - if !extraRespInfo.bidsFound && adapterBids[brw.bidder] != nil && len(adapterBids[brw.bidder].Bids) > 0 { - extraRespInfo.bidsFound = true - } + // collect adapter non bids + extraRespInfo.seatNonBidBuilder.append(brw.seatNonBidBuilder) + } return adapterBids, adapterExtra, extraRespInfo @@ -885,7 +933,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 *openrtb2.BidRequest, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, pubID string, errList []error) *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, seatNonBidBuilder *SeatNonBidBuilder) *openrtb2.BidResponse { bidResponse := new(openrtb2.BidResponse) bidResponse.ID = bidRequest.ID @@ -900,7 +948,7 @@ func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ for a, adapterSeatBids := range adapterSeatBids { //while processing every single bib, do we need to handle categories here? if adapterSeatBids != nil && len(adapterSeatBids.Bids) > 0 { - sb := e.makeSeatBid(adapterSeatBids, a, adapterExtra, auc, returnCreative, impExtInfoMap, bidResponseExt, pubID) + sb := e.makeSeatBid(adapterSeatBids, a, adapterExtra, auc, returnCreative, impExtInfoMap, bidRequest, bidResponseExt, pubID, seatNonBidBuilder) seatBids = append(seatBids, *sb) bidResponse.Cur = adapterSeatBids.Currency } @@ -920,7 +968,7 @@ func encodeBidResponseExt(bidResponseExt *openrtb_ext.ExtBidResponse) ([]byte, e return buffer.Bytes(), err } -func applyCategoryMapping(ctx context.Context, 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, targeting openrtb_ext.ExtRequestTargeting, seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData, booleanGenerator deduplicateChanceGenerator, seatNonBidBuilder *SeatNonBidBuilder) (map[string]string, map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, []string, error) { res := make(map[string]string) type bidDedupe struct { @@ -982,7 +1030,7 @@ func applyCategoryMapping(ctx context.Context, targeting openrtb_ext.ExtRequestT //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(ResponseRejectedCategoryMappingInvalid), string(bidderName)) + seatNonBidBuilder.rejectBid(bid, int(ResponseRejectedCategoryMappingInvalid), string(bidderName)) continue } if translateCategories { @@ -1219,14 +1267,14 @@ 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, bidResponseExt *openrtb_ext.ExtBidResponse, pubID string) *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, seatNonBidBuilder *SeatNonBidBuilder) *openrtb2.SeatBid { seatBid := &openrtb2.SeatBid{ Seat: adapter.String(), Group: 0, // Prebid cannot support roadblocking } var errList []error - seatBid.Bid, errList = e.makeBid(adapterBid.Bids, auc, returnCreative, impExtInfoMap, bidResponseExt, adapter, pubID) + seatBid.Bid, errList = e.makeBid(adapterBid.Bids, auc, returnCreative, impExtInfoMap, bidRequest, bidResponseExt, adapter, pubID, seatNonBidBuilder) if len(errList) > 0 { adapterExtra[adapter].Errors = append(adapterExtra[adapter].Errors, errsToBidderErrors(errList)...) } @@ -1234,13 +1282,24 @@ 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, bidResponseExt *openrtb_ext.ExtBidResponse, adapter openrtb_ext.BidderName, pubID string) ([]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, seatNonBidBuilder *SeatNonBidBuilder) ([]openrtb2.Bid, []error) { result := make([]openrtb2.Bid, 0, len(bids)) errs := make([]error, 0, 1) for _, bid := range bids { + if err := dsa.Validate(bidRequest, bid); err != nil { + dsaMessage := openrtb_ext.ExtBidderMessage{ + Code: errortypes.InvalidBidResponseDSAWarningCode, + Message: fmt.Sprintf("bid rejected: %s", err.Error()), + } + bidResponseExt.Warnings[adapter] = append(bidResponseExt.Warnings[adapter], dsaMessage) + + seatNonBidBuilder.rejectBid(bid, int(ResponseRejectedGeneral), 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) { + seatNonBidBuilder.rejectBid(bid, int(ResponseRejectedCreativeSizeNotAllowed), adapter.String()) continue // Don't add bid to result } } else if e.bidValidationEnforcement.BannerCreativeMaxSize == config.ValidationWarn && bid.BidType == openrtb_ext.BidTypeBanner { @@ -1249,6 +1308,7 @@ 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) { + seatNonBidBuilder.rejectBid(bid, int(ResponseRejectedCreativeNotSecure), 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) { @@ -1275,7 +1335,7 @@ func (e *exchange) makeBid(bids []*entities.PbsOrtbBid, auc *auction, returnCrea } } - if bidExtJSON, err := makeBidExtJSON(bid.Bid.Ext, bidExtPrebid, impExtInfoMap, bid.Bid.ImpID, bid.OriginalBidCPM, bid.OriginalBidCur); err != nil { + if bidExtJSON, err := makeBidExtJSON(bid.Bid.Ext, bidExtPrebid, impExtInfoMap, bid.Bid.ImpID, bid.OriginalBidCPM, bid.OriginalBidCur, bid.AdapterCode); err != nil { errs = append(errs, err) } else { result = append(result, *bid.Bid) @@ -1289,7 +1349,7 @@ func (e *exchange) makeBid(bids []*entities.PbsOrtbBid, auc *auction, returnCrea return result, errs } -func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impExtInfoMap map[string]ImpExtInfo, impId string, originalBidCpm float64, originalBidCur string) (json.RawMessage, error) { +func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impExtInfoMap map[string]ImpExtInfo, impId string, originalBidCpm float64, originalBidCur string, adapter openrtb_ext.BidderName) (json.RawMessage, error) { var extMap map[string]interface{} if len(ext) != 0 { @@ -1318,11 +1378,17 @@ func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impEx } `json:"prebid"` }{} if err := jsonutil.Unmarshal(ext, &metaContainer); err != nil { - return nil, fmt.Errorf("error validaing response from server, %s", err) + return nil, fmt.Errorf("error validating response from server, %s", err) } prebid.Meta = &metaContainer.Prebid.Meta } + if prebid.Meta == nil { + prebid.Meta = &openrtb_ext.ExtBidPrebidMeta{} + } + + prebid.Meta.AdapterCode = adapter.String() + // ext.prebid.storedrequestattributes and ext.prebid.passthrough if impExtInfo, ok := impExtInfoMap[impId]; ok { prebid.Passthrough = impExtInfoMap[impId].Passthrough @@ -1354,34 +1420,6 @@ func (e *exchange) getBidCacheInfo(bid *entities.PbsOrtbBid, auction *auction) ( return } -func (e *exchange) getAuctionCurrencyRates(requestRates *openrtb_ext.ExtRequestCurrency) currency.Conversions { - if requestRates == nil { - // No bidRequest.ext.currency field was found, use PBS rates as usual - return e.currencyConverter.Rates() - } - - // If bidRequest.ext.currency.usepbsrates is nil, we understand its value as true. It will be false - // only if it's explicitly set to false - usePbsRates := requestRates.UsePBSRates == nil || *requestRates.UsePBSRates - - if !usePbsRates { - // At this point, we can safely assume the ConversionRates map is not empty because - // validateCustomRates(bidReqCurrencyRates *openrtb_ext.ExtRequestCurrency) would have - // thrown an error under such conditions. - return currency.NewRates(requestRates.ConversionRates) - } - - // Both PBS and custom rates can be used, check if ConversionRates is not empty - if len(requestRates.ConversionRates) == 0 { - // Custom rates map is empty, use PBS rates only - return e.currencyConverter.Rates() - } - - // Return an AggregateConversions object that includes both custom and PBS currency rates but will - // prioritize custom rates over PBS rates whenever a currency rate is found in both - return currency.NewAggregateConversions(currency.NewRates(requestRates.ConversionRates), e.currencyConverter.Rates()) -} - func findCacheID(bid *entities.PbsOrtbBid, auction *auction) (string, bool) { if bid != nil && bid.Bid != nil && auction != nil { if id, found := auction.cacheIds[bid.Bid]; found { @@ -1570,8 +1608,8 @@ func setErrorMessageSecureMarkup(validationType string) string { } // setSeatNonBid adds SeatNonBids within bidResponse.Ext.Prebid.SeatNonBid -func setSeatNonBid(bidResponseExt *openrtb_ext.ExtBidResponse, seatNonBids nonBids) *openrtb_ext.ExtBidResponse { - if len(seatNonBids.seatNonBidsMap) == 0 { +func setSeatNonBid(bidResponseExt *openrtb_ext.ExtBidResponse, seatNonBidBuilder SeatNonBidBuilder) *openrtb_ext.ExtBidResponse { + if len(seatNonBidBuilder) == 0 { return bidResponseExt } if bidResponseExt == nil { @@ -1581,6 +1619,6 @@ func setSeatNonBid(bidResponseExt *openrtb_ext.ExtBidResponse, seatNonBids nonBi bidResponseExt.Prebid = &openrtb_ext.ExtResponsePrebid{} } - bidResponseExt.Prebid.SeatNonBid = seatNonBids.get() + bidResponseExt.Prebid.SeatNonBid = seatNonBidBuilder.Slice() return bidResponseExt } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index ff989bad91d..87b53b101e0 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "fmt" - "io" "math" "net/http" "net/http/httptest" @@ -20,29 +19,31 @@ import ( "time" "github.com/buger/jsonparser" - "github.com/prebid/openrtb/v19/openrtb2" - "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/exchange/entities" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - metricsConf "github.com/prebid/prebid-server/metrics/config" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - pbc "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/file_fetcher" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/experiment/adscert" + "github.com/prebid/prebid-server/v3/gdpr" + "github.com/prebid/prebid-server/v3/hooks" + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/hooks/hookstage" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/metrics" + metricsConf "github.com/prebid/prebid-server/v3/metrics/config" + metricsConfig "github.com/prebid/prebid-server/v3/metrics/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/ortb" + pbc "github.com/prebid/prebid-server/v3/prebid_cache_client" + "github.com/prebid/prebid-server/v3/privacy" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/stored_requests/backends/file_fetcher" + "github.com/prebid/prebid-server/v3/stored_responses" + "github.com/prebid/prebid-server/v3/usersync" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" jsonpatch "gopkg.in/evanphx/json-patch.v4" @@ -83,7 +84,7 @@ func TestNewExchange(t *testing.T) { }, }.Builder - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, nil, cfg, &mockRequestValidator{}, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) for _, bidderName := range knownAdapters { if _, ok := e.adapterMap[bidderName]; !ok { if biddersInfo[string(bidderName)].IsEnabled() { @@ -133,7 +134,7 @@ func TestCharacterEscape(t *testing.T) { }, }.Builder - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, nil, cfg, &mockRequestValidator{}, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs //liveAdapters []openrtb_ext.BidderName, @@ -145,18 +146,20 @@ func TestCharacterEscape(t *testing.T) { adapterBids["appnexus"] = &entities.PbsOrtbSeatBid{Currency: "USD"} //An openrtb2.BidRequest struct as specified in https://github.com/prebid/prebid-server/issues/465 - bidRequest := &openrtb2.BidRequest{ - ID: "some-request-id", - Imp: []openrtb2.Imp{{ - ID: "some-impression-id", - Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, - Ext: json.RawMessage(`{"appnexus": {"placementId": 1}}`), - }}, - Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, - Device: &openrtb2.Device{UA: "curl/7.54.0", IP: "::1"}, - AT: 1, - TMax: 500, - Ext: json.RawMessage(`{"id": "some-request-id","site": {"page": "prebid.org"},"imp": [{"id": "some-impression-id","banner": {"format": [{"w": 300,"h": 250},{"w": 300,"h": 600}]},"ext": {"appnexus": {"placementId": 1}}}],"tmax": 500}`), + bidRequest := &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id", + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, + Ext: json.RawMessage(`{"appnexus": {"placementId": 1}}`), + }}, + Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + Device: &openrtb2.Device{UA: "curl/7.54.0", IP: "::1"}, + AT: 1, + TMax: 500, + Ext: json.RawMessage(`{"id": "some-request-id","site": {"page": "prebid.org"},"imp": [{"id": "some-impression-id","banner": {"format": [{"w": 300,"h": 250},{"w": 300,"h": 600}]},"ext": {"appnexus": {"placementId": 1}}}],"tmax": 500}`), + }, } //adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, @@ -169,7 +172,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) + bidResp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, nil, nil, true, nil, "", errList, &SeatNonBidBuilder{}) // 5) Assert we have no errors and one '&' character as we are supposed to if len(errList) > 0 { @@ -364,7 +367,7 @@ func TestDebugBehaviour(t *testing.T) { if test.generateWarnings { var errL []error errL = append(errL, &errortypes.Warning{ - Message: fmt.Sprintf("CCPA consent test warning."), + Message: "CCPA consent test warning.", WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) auctionRequest.Warnings = errL } @@ -573,9 +576,8 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { } func TestOverrideWithCustomCurrency(t *testing.T) { - - mockCurrencyClient := &fakeCurrencyRatesHttpClient{ - responseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`, + mockCurrencyClient := ¤cy.MockCurrencyRatesHttpClient{ + ResponseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`, } mockCurrencyConverter := currency.NewRateConverter( mockCurrencyClient, @@ -663,7 +665,7 @@ func TestOverrideWithCustomCurrency(t *testing.T) { }.Builder e.currencyConverter = mockCurrencyConverter e.categoriesFetcher = categoriesFetcher - e.bidIDGenerator = &mockBidIDGenerator{false, false} + e.bidIDGenerator = &fakeBidIDGenerator{GenerateBidID: false, ReturnError: false} e.requestSplitter = requestSplitter{ me: e.me, gdprPermsBuilder: e.gdprPermsBuilder, @@ -741,11 +743,11 @@ func TestOverrideWithCustomCurrency(t *testing.T) { } func TestAdapterCurrency(t *testing.T) { - fakeCurrencyClient := &fakeCurrencyRatesHttpClient{ - responseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`, + mockCurrencyClient := ¤cy.MockCurrencyRatesHttpClient{ + ResponseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`, } currencyConverter := currency.NewRateConverter( - fakeCurrencyClient, + mockCurrencyClient, "currency.fake.com", 24*time.Hour, ) @@ -768,7 +770,7 @@ func TestAdapterCurrency(t *testing.T) { }.Builder, currencyConverter: currencyConverter, categoriesFetcher: nilCategoryFetcher{}, - bidIDGenerator: &mockBidIDGenerator{false, false}, + bidIDGenerator: &fakeBidIDGenerator{GenerateBidID: false, ReturnError: false}, adapterMap: map[openrtb_ext.BidderName]AdaptedBidder{ openrtb_ext.BidderName("appnexus"): AdaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderName("appnexus"), nil, ""), }, @@ -816,13 +818,20 @@ func TestAdapterCurrency(t *testing.T) { } } -func TestFloorsSignalling(t *testing.T) { +type mockPriceFloorFetcher struct{} + +func (mpf *mockPriceFloorFetcher) Fetch(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) { + return nil, openrtb_ext.FetchNone +} + +func (mpf *mockPriceFloorFetcher) Stop() {} - fakeCurrencyClient := &fakeCurrencyRatesHttpClient{ - responseBody: `{"dataAsOf":"2023-04-10","conversions":{"USD":{"MXN":10.00}}}`, +func TestFloorsSignalling(t *testing.T) { + mockCurrencyClient := ¤cy.MockCurrencyRatesHttpClient{ + ResponseBody: `{"dataAsOf":"2023-04-10","conversions":{"USD":{"MXN":10.00}}}`, } currencyConverter := currency.NewRateConverter( - fakeCurrencyClient, + mockCurrencyClient, "currency.com", 24*time.Hour, ) @@ -839,8 +848,9 @@ func TestFloorsSignalling(t *testing.T) { }.Builder, currencyConverter: currencyConverter, categoriesFetcher: nilCategoryFetcher{}, - bidIDGenerator: &mockBidIDGenerator{false, false}, - floor: config.PriceFloors{Enabled: true}, + bidIDGenerator: &fakeBidIDGenerator{GenerateBidID: false, ReturnError: false}, + priceFloorEnabled: true, + priceFloorFetcher: &mockPriceFloorFetcher{}, } e.requestSplitter = requestSplitter{ me: e.me, @@ -970,6 +980,7 @@ func TestFloorsSignalling(t *testing.T) { Account: config.Account{DebugAllow: true, PriceFloors: config.AccountPriceFloors{Enabled: test.floorsEnable, MaxRule: 100, MaxSchemaDims: 5}}, UserSyncs: &emptyUsersync{}, HookExecutor: &hookexecution.EmptyHookExecutor{}, + TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), } outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{}) @@ -985,208 +996,6 @@ func TestFloorsSignalling(t *testing.T) { } } -func TestGetAuctionCurrencyRates(t *testing.T) { - - pbsRates := map[string]map[string]float64{ - "MXN": { - "USD": 20.13, - "EUR": 27.82, - "JPY": 5.09, // "MXN" to "JPY" rate not found in customRates - }, - } - - customRates := map[string]map[string]float64{ - "MXN": { - "USD": 25.00, // different rate than in pbsRates - "EUR": 27.82, // same as in pbsRates - "GBP": 31.12, // not found in pbsRates at all - }, - } - - expectedRateEngineRates := map[string]map[string]float64{ - "MXN": { - "USD": 25.00, // rates engine will prioritize the value found in custom rates - "EUR": 27.82, // same value in both the engine reads the custom entry first - "JPY": 5.09, // the engine will find it in the pbsRates conversions - "GBP": 31.12, // the engine will find it in the custom conversions - }, - } - - boolTrue := true - boolFalse := false - - type testInput struct { - pbsRates map[string]map[string]float64 - bidExtCurrency *openrtb_ext.ExtRequestCurrency - } - type testOutput struct { - constantRates bool - resultingRates map[string]map[string]float64 - } - testCases := []struct { - desc string - given testInput - expected testOutput - }{ - { - "valid pbsRates, valid ConversionRates, false UsePBSRates. Resulting rates identical to customRates", - testInput{ - pbsRates: pbsRates, - bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ - ConversionRates: customRates, - UsePBSRates: &boolFalse, - }, - }, - testOutput{ - resultingRates: customRates, - }, - }, - { - "valid pbsRates, valid ConversionRates, true UsePBSRates. Resulting rates are a mix but customRates gets priority", - testInput{ - pbsRates: pbsRates, - bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ - ConversionRates: customRates, - UsePBSRates: &boolTrue, - }, - }, - testOutput{ - resultingRates: expectedRateEngineRates, - }, - }, - { - "nil pbsRates, valid ConversionRates, false UsePBSRates. Resulting rates identical to customRates", - testInput{ - pbsRates: nil, - bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ - ConversionRates: customRates, - UsePBSRates: &boolFalse, - }, - }, - testOutput{ - resultingRates: customRates, - }, - }, - { - "nil pbsRates, valid ConversionRates, true UsePBSRates. Resulting rates identical to customRates", - testInput{ - pbsRates: nil, - bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ - ConversionRates: customRates, - UsePBSRates: &boolTrue, - }, - }, - testOutput{ - resultingRates: customRates, - }, - }, - { - "valid pbsRates, empty ConversionRates, false UsePBSRates. Because pbsRates cannot be used, default to constant rates", - testInput{ - pbsRates: pbsRates, - bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ - // ConversionRates inCustomRates not initialized makes for a zero-length map - UsePBSRates: &boolFalse, - }, - }, - testOutput{ - constantRates: true, - }, - }, - { - "valid pbsRates, nil ConversionRates, UsePBSRates defaults to true. Resulting rates will be identical to pbsRates", - testInput{ - pbsRates: pbsRates, - bidExtCurrency: nil, - }, - testOutput{ - resultingRates: pbsRates, - }, - }, - { - "nil pbsRates, empty ConversionRates, false UsePBSRates. Default to constant rates", - testInput{ - pbsRates: nil, - bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ - // ConversionRates inCustomRates not initialized makes for a zero-length map - UsePBSRates: &boolFalse, - }, - }, - testOutput{ - constantRates: true, - }, - }, - { - "customRates empty, UsePBSRates set to true, pbsRates are nil. Return default constant rates converter", - testInput{ - pbsRates: nil, - bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ - // ConversionRates inCustomRates not initialized makes for a zero-length map - UsePBSRates: &boolTrue, - }, - }, - testOutput{ - constantRates: true, - }, - }, - { - "nil customRates, nil pbsRates, UsePBSRates defaults to true. Return default constant rates converter", - testInput{ - pbsRates: nil, - bidExtCurrency: nil, - }, - testOutput{ - constantRates: true, - }, - }, - } - - for _, tc := range testCases { - - // Test setup: - jsonPbsRates, err := jsonutil.Marshal(tc.given.pbsRates) - if err != nil { - t.Fatalf("Failed to marshal PBS rates: %v", err) - } - - // Init mock currency conversion service - mockCurrencyClient := &fakeCurrencyRatesHttpClient{ - responseBody: `{"dataAsOf":"2018-09-12","conversions":` + string(jsonPbsRates) + `}`, - } - mockCurrencyConverter := currency.NewRateConverter( - mockCurrencyClient, - "currency.fake.com", - 24*time.Hour, - ) - mockCurrencyConverter.Run() - - e := new(exchange) - e.currencyConverter = mockCurrencyConverter - - // Run test - auctionRates := e.getAuctionCurrencyRates(tc.given.bidExtCurrency) - - // When fromCurrency and toCurrency are the same, a rate of 1.00 is always expected - rate, err := auctionRates.GetRate("USD", "USD") - assert.NoError(t, err, tc.desc) - assert.Equal(t, float64(1), rate, tc.desc) - - // If we expect an error, assert we have one along with a conversion rate of zero - if tc.expected.constantRates { - rate, err := auctionRates.GetRate("USD", "MXN") - assert.Error(t, err, tc.desc) - assert.Equal(t, float64(0), rate, tc.desc) - } else { - for fromCurrency, rates := range tc.expected.resultingRates { - for toCurrency, expectedRate := range rates { - actualRate, err := auctionRates.GetRate(fromCurrency, toCurrency) - assert.NoError(t, err, tc.desc) - assert.Equal(t, expectedRate, actualRate, tc.desc) - } - } - } - } -} func TestReturnCreativeEndToEnd(t *testing.T) { sampleAd := "" @@ -1323,7 +1132,7 @@ func TestReturnCreativeEndToEnd(t *testing.T) { }.Builder e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) e.categoriesFetcher = categoriesFetcher - e.bidIDGenerator = &mockBidIDGenerator{false, false} + e.bidIDGenerator = &fakeBidIDGenerator{GenerateBidID: false, ReturnError: false} e.requestSplitter = requestSplitter{ me: e.me, gdprPermsBuilder: e.gdprPermsBuilder, @@ -1428,7 +1237,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { }, }.Builder - e := NewExchange(adapters, pbc, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, pbc, cfg, &mockRequestValidator{}, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs liveAdapters := []openrtb_ext.BidderName{bidderName} @@ -1499,40 +1308,42 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { }, }, } - bidRequest := &openrtb2.BidRequest{ - ID: "some-request-id", - TMax: 1000, - Imp: []openrtb2.Imp{ - { - ID: "test-div", - Secure: openrtb2.Int8Ptr(0), - Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}, - Ext: json.RawMessage(` { - "rubicon": { - "accountId": 1001, - "siteId": 113932, - "zoneId": 535510 - }, - "appnexus": { "placementId": 1 }, - "pubmatic": { "publisherId": "156209", "adSlot": "pubmatic_test2@300x250" }, - "pulsepoint": { "cf": "300X250", "cp": 512379, "ct": 486653 }, - "conversant": { "site_id": "108060" }, - "ix": { "siteId": "287415" } -}`), + bidRequest := &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "some-request-id", + TMax: 1000, + Imp: []openrtb2.Imp{ + { + ID: "test-div", + Secure: openrtb2.Int8Ptr(0), + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}, + Ext: json.RawMessage(` { + "rubicon": { + "accountId": 1001, + "siteId": 113932, + "zoneId": 535510 + }, + "appnexus": { "placementId": 1 }, + "pubmatic": { "publisherId": "156209", "adSlot": "pubmatic_test2@300x250" }, + "pulsepoint": { "cf": "300X250", "cp": 512379, "ct": 486653 }, + "conversant": { "site_id": "108060" }, + "ix": { "siteId": "287415" } + }`), + }, }, + Site: &openrtb2.Site{ + Page: "http://rubitest.com/index.html", + Publisher: &openrtb2.Publisher{ID: "1001"}, + }, + Test: 1, + Ext: json.RawMessage(`{"prebid": { "cache": { "bids": {}, "vastxml": {} }, "targeting": { "pricegranularity": "med", "includewinners": true, "includebidderkeys": false } }}`), }, - Site: &openrtb2.Site{ - Page: "http://rubitest.com/index.html", - Publisher: &openrtb2.Publisher{ID: "1001"}, - }, - Test: 1, - Ext: json.RawMessage(`{"prebid": { "cache": { "bids": {}, "vastxml": {} }, "targeting": { "pricegranularity": "med", "includewinners": true, "includebidderkeys": false } }}`), } var errList []error // 4) Build bid response - bid_resp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, auc, nil, true, nil, "", errList) + bid_resp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, auc, nil, true, nil, "", errList, &SeatNonBidBuilder{}) expectedBidResponse := &openrtb2.BidResponse{ SeatBid: []openrtb2.SeatBid{ @@ -1622,7 +1433,7 @@ func TestBidReturnsCreative(t *testing.T) { //Run tests for _, test := range testCases { - resultingBids, resultingErrs := e.makeBid(sampleBids, sampleAuction, test.inReturnCreative, nil, nil, "", "") + resultingBids, resultingErrs := e.makeBid(sampleBids, sampleAuction, test.inReturnCreative, nil, &openrtb_ext.RequestWrapper{}, nil, "", "", &SeatNonBidBuilder{}) 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) @@ -1785,23 +1596,25 @@ func TestBidResponseCurrency(t *testing.T) { }, }.Builder - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, nil, cfg, &mockRequestValidator{}, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" - bidRequest := &openrtb2.BidRequest{ - ID: "some-request-id", - Imp: []openrtb2.Imp{{ - ID: "some-impression-id", - Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, - Ext: json.RawMessage(`{"appnexus": {"placementId": 10433394}}`), - }}, - Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, - Device: &openrtb2.Device{UA: "curl/7.54.0", IP: "::1"}, - AT: 1, - TMax: 500, - Ext: json.RawMessage(`{"id": "some-request-id","site": {"page": "prebid.org"},"imp": [{"id": "some-impression-id","banner": {"format": [{"w": 300,"h": 250},{"w": 300,"h": 600}]},"ext": {"appnexus": {"placementId": 10433394}}}],"tmax": 500}`), + bidRequest := &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id", + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, + Ext: json.RawMessage(`{"appnexus": {"placementId": 10433394}}`), + }}, + Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + Device: &openrtb2.Device{UA: "curl/7.54.0", IP: "::1"}, + AT: 1, + TMax: 500, + Ext: json.RawMessage(`{"id": "some-request-id","site": {"page": "prebid.org"},"imp": [{"id": "some-impression-id","banner": {"format": [{"w": 300,"h": 250},{"w": 300,"h": 600}]},"ext": {"appnexus": {"placementId": 10433394}}}],"tmax": 500}`), + }, } adapterExtra := map[openrtb_ext.BidderName]*seatResponseExtra{ @@ -1827,7 +1640,7 @@ func TestBidResponseCurrency(t *testing.T) { Price: 9.517803, W: 300, H: 250, - Ext: json.RawMessage(`{"origbidcpm":9.517803,"prebid":{"type":"banner"}}`), + Ext: json.RawMessage(`{"origbidcpm":9.517803,"prebid":{"meta":{},"type":"banner"}}`), }, }, }, @@ -1905,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) + actualBidResp := e.buildBidResponse(context.Background(), liveAdapters, testCases[i].adapterBids, bidRequest, adapterExtra, nil, bidResponseExt, true, nil, "", errList, &SeatNonBidBuilder{}) 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)) } } @@ -1931,19 +1744,21 @@ func TestBidResponseImpExtInfo(t *testing.T) { t.Fatalf("Error intializing adapters: %v", adaptersErr) } - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, nil, gdprPermsBuilder, nil, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, nil, cfg, &mockRequestValidator{}, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, nil, gdprPermsBuilder, nil, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" - bidRequest := &openrtb2.BidRequest{ - ID: "some-request-id", - Imp: []openrtb2.Imp{{ - ID: "some-impression-id", - Video: &openrtb2.Video{}, - Ext: json.RawMessage(`{"appnexus": {"placementId": 10433394}}`), - }}, - Ext: json.RawMessage(``), + bidRequest := &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id", + Video: &openrtb2.Video{}, + Ext: json.RawMessage(`{"appnexus": {"placementId": 10433394}}`), + }}, + Ext: json.RawMessage(``), + }, } var errList []error @@ -1955,7 +1770,7 @@ func TestBidResponseImpExtInfo(t *testing.T) { H: 250, Ext: nil, } - aPbsOrtbBidArr := []*entities.PbsOrtbBid{{Bid: sampleBid, BidType: openrtb_ext.BidTypeVideo}} + aPbsOrtbBidArr := []*entities.PbsOrtbBid{{Bid: sampleBid, BidType: openrtb_ext.BidTypeVideo, AdapterCode: openrtb_ext.BidderAppnexus}} adapterBids := map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ openrtb_ext.BidderName("appnexus"): { @@ -1969,9 +1784,9 @@ func TestBidResponseImpExtInfo(t *testing.T) { []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val":1}`)} - expectedBidResponseExt := `{"origbidcpm":0,"prebid":{"type":"video","passthrough":{"imp_passthrough_val":1}},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}` + 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) + actualBidResp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, nil, nil, nil, true, impExtInfo, "", errList, &SeatNonBidBuilder{}) resBidExt := string(actualBidResp.SeatBid[0].Bid[0].Ext) assert.Equalf(t, expectedBidResponseExt, resBidExt, "Expected bid response extension is incorrect") @@ -2023,7 +1838,7 @@ func TestRaceIntegration(t *testing.T) { }, }.Builder - ex := NewExchange(adapters, &wellBehavedCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, &nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + ex := NewExchange(adapters, &wellBehavedCache{}, cfg, &mockRequestValidator{}, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, &nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) _, err = ex.HoldAuction(context.Background(), auctionRequest, &debugLog) if err != nil { t.Errorf("HoldAuction returned unexpected error: %v", err) @@ -2088,8 +1903,8 @@ func getTestBuildRequest(t *testing.T) *openrtb2.BidRequest { MIMEs: []string{"video/mp4"}, MinDuration: 1, MaxDuration: 300, - W: 300, - H: 600, + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](600), }, Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}}}}`), }}, @@ -2121,7 +1936,7 @@ func TestPanicRecovery(t *testing.T) { }, }.Builder - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, nil, cfg, &mockRequestValidator{}, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) chBids := make(chan *bidResponseWrapper, 1) panicker := func(bidderRequest BidderRequest, conversions currency.Conversions) { @@ -2191,7 +2006,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) { allowAllBidders: true, }, }.Builder - e := NewExchange(adapters, &mockCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, categoriesFetcher, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, &mockCache{}, cfg, &mockRequestValidator{}, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, categoriesFetcher, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange) e.adapterMap[openrtb_ext.BidderBeachfront] = panicingAdapter{} e.adapterMap[openrtb_ext.BidderAppnexus] = panicingAdapter{} @@ -2293,8 +2108,8 @@ func loadFile(filename string) (*exchangeSpec, error) { } func runSpec(t *testing.T, filename string, spec *exchangeSpec) { - aliases, errs := parseAliases(&spec.IncomingRequest.OrtbRequest) - if len(errs) != 0 { + aliases, err := parseRequestAliases(spec.IncomingRequest.OrtbRequest) + if err != nil { t.Fatalf("%s: Failed to parse aliases", filename) } @@ -2327,11 +2142,11 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { }, }, } - bidIdGenerator := &mockBidIDGenerator{} + bidIdGenerator := &fakeBidIDGenerator{} if spec.BidIDGenerator != nil { - *bidIdGenerator = *spec.BidIDGenerator + bidIdGenerator = spec.BidIDGenerator } - ex := newExchangeForTests(t, filename, spec.OutgoingRequests, aliases, privacyConfig, bidIdGenerator, spec.HostSChainFlag, spec.FloorsEnabled, spec.HostConfigBidValidation, spec.Server) + ex := newExchangeForTests(t, filename, aliases, privacyConfig, bidIdGenerator, spec) biddersInAuction := findBiddersInAuction(t, filename, &spec.IncomingRequest.OrtbRequest) debugLog := &DebugLog{} if spec.DebugLog != nil { @@ -2358,7 +2173,13 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { impExtInfoMap[impID] = ImpExtInfo{} } - activityControl := privacy.NewActivityControl(spec.AccountPrivacy) + if spec.AccountPrivacy.DSA != nil && len(spec.AccountPrivacy.DSA.Default) > 0 { + if err := jsonutil.Unmarshal([]byte(spec.AccountPrivacy.DSA.Default), &spec.AccountPrivacy.DSA.DefaultUnpacked); err != nil { + t.Errorf("%s: Exchange returned an unexpected error. Got %s", filename, err.Error()) + } + } + + activityControl := privacy.NewActivityControl(&spec.AccountPrivacy) auctionRequest := &AuctionRequest{ BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &spec.IncomingRequest.OrtbRequest}, @@ -2368,7 +2189,8 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { Enabled: spec.EventsEnabled, }, DebugAllow: true, - PriceFloors: config.AccountPriceFloors{Enabled: spec.AccountFloorsEnabled}, + PriceFloors: config.AccountPriceFloors{Enabled: spec.AccountFloorsEnabled, EnforceDealFloors: spec.AccountEnforceDealFloors}, + Privacy: spec.AccountPrivacy, Validations: spec.AccountConfigBidValidation, }, UserSyncs: mockIdFetcher(spec.IncomingRequest.Usersyncs), @@ -2408,8 +2230,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 if aucResponse != nil { bid = aucResponse.BidResponse + bidExt = aucResponse.ExtBidResponse } if len(spec.Response.Error) > 0 && spec.Response.Bids == nil { if err.Error() != spec.Response.Error { @@ -2492,24 +2316,29 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { assert.JSONEq(t, string(spec.Response.Ext), string(bid.Ext), "ext mismatch") } + expectedBidRespExt := &openrtb_ext.ExtBidResponse{} + if spec.Response.Ext != nil { + if err := jsonutil.UnmarshalValid(spec.Response.Ext, expectedBidRespExt); err != nil { + assert.NoError(t, err, fmt.Sprintf("Error when unmarshalling: %s", err)) + } + } if spec.HostConfigBidValidation.BannerCreativeMaxSize == config.ValidationEnforce || spec.HostConfigBidValidation.SecureMarkup == config.ValidationEnforce { actualBidRespExt := &openrtb_ext.ExtBidResponse{} - expectedBidRespExt := &openrtb_ext.ExtBidResponse{} if bid.Ext != nil { if err := jsonutil.UnmarshalValid(bid.Ext, actualBidRespExt); err != nil { assert.NoError(t, err, fmt.Sprintf("Error when unmarshalling: %s", err)) } } - if err := jsonutil.UnmarshalValid(spec.Response.Ext, expectedBidRespExt); err != nil { - assert.NoError(t, err, fmt.Sprintf("Error when unmarshalling: %s", err)) - } - 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") + } } func findBiddersInAuction(t *testing.T, context string, req *openrtb2.BidRequest) []string { - if splitImps, err := splitImps(req.Imp); err != nil { + + if splitImps, err := splitImps(req.Imp, &mockRequestValidator{}, nil, false, nil); err != nil { t.Errorf("%s: Failed to parse Bidders from request: %v", context, err) return nil } else { @@ -2545,11 +2374,11 @@ func extractResponseTimes(t *testing.T, context string, bid *openrtb2.BidRespons } } -func newExchangeForTests(t *testing.T, filename string, expectations map[string]*bidderSpec, aliases map[string]string, privacyConfig config.Privacy, bidIDGenerator BidIDGenerator, hostSChainFlag, floorsFlag bool, hostBidValidation config.Validations, server exchangeServer) Exchange { - bidderAdapters := make(map[openrtb_ext.BidderName]AdaptedBidder, len(expectations)) - bidderInfos := make(config.BidderInfos, len(expectations)) +func newExchangeForTests(t *testing.T, filename string, aliases map[string]string, privacyConfig config.Privacy, bidIDGenerator BidIDGenerator, exSpec *exchangeSpec) Exchange { + bidderAdapters := make(map[openrtb_ext.BidderName]AdaptedBidder, len(exSpec.OutgoingRequests)) + bidderInfos := make(config.BidderInfos, len(exSpec.OutgoingRequests)) for _, bidderName := range openrtb_ext.CoreBidderNames() { - if spec, ok := expectations[string(bidderName)]; ok { + if spec, ok := exSpec.OutgoingRequests[string(bidderName)]; ok { bidderAdapters[bidderName] = &validatingBidder{ t: t, fileName: filename, @@ -2557,12 +2386,18 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] expectations: map[string]*bidderRequest{string(bidderName): spec.ExpectedRequest}, mockResponses: map[string]bidderResponse{string(bidderName): spec.MockResponse}, } - bidderInfos[string(bidderName)] = config.BidderInfo{ModifyingVastXmlAllowed: spec.ModifyingVastXmlAllowed} + ortbVersion, _ := exSpec.ORTBVersion[string(bidderName)] + bidderInfos[string(bidderName)] = config.BidderInfo{ + ModifyingVastXmlAllowed: spec.ModifyingVastXmlAllowed, + OpenRTB: &config.OpenRTBInfo{Version: ortbVersion}, + } + } else { + bidderInfos[string(bidderName)] = config.BidderInfo{} } } for alias, coreBidder := range aliases { - if spec, ok := expectations[alias]; ok { + if spec, ok := exSpec.OutgoingRequests[alias]; ok { if bidder, ok := bidderAdapters[openrtb_ext.BidderName(coreBidder)]; ok { bidder.(*validatingBidder).expectations[alias] = spec.ExpectedRequest bidder.(*validatingBidder).mockResponses[alias] = spec.MockResponse @@ -2600,13 +2435,19 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] } var hostSChainNode *openrtb2.SupplyChainNode - if hostSChainFlag { + if exSpec.HostSChainFlag { hostSChainNode = &openrtb2.SupplyChainNode{ ASI: "pbshostcompany.com", SID: "00001", RID: "BidRequest", HP: openrtb2.Int8Ptr(1), } } metricsEngine := metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames(), nil, nil) + paramValidator, err := openrtb_ext.NewBidderParamsValidator("../static/bidder-params") + if err != nil { + t.Fatalf("Failed to create params validator: %v", error) + } + bidderMap := GetActiveBidders(bidderInfos) + requestValidator := ortb.NewRequestValidator(bidderMap, map[string]string{}, paramValidator) requestSplitter := requestSplitter{ bidderToSyncerKey: bidderToSyncerKey, me: metricsEngine, @@ -2614,6 +2455,7 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] gdprPermsBuilder: gdprPermsBuilder, hostSChainNode: hostSChainNode, bidderInfo: bidderInfos, + requestValidator: requestValidator, } return &exchange{ @@ -2631,38 +2473,43 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] externalURL: "http://localhost", bidIDGenerator: bidIDGenerator, hostSChainNode: hostSChainNode, - server: config.Server{ExternalUrl: server.ExternalUrl, GvlID: server.GvlID, DataCenter: server.DataCenter}, - bidValidationEnforcement: hostBidValidation, + server: config.Server{ExternalUrl: exSpec.Server.ExternalUrl, GvlID: exSpec.Server.GvlID, DataCenter: exSpec.Server.DataCenter}, + bidValidationEnforcement: exSpec.HostConfigBidValidation, requestSplitter: requestSplitter, - floor: config.PriceFloors{Enabled: floorsFlag}, + priceFloorEnabled: exSpec.FloorsEnabled, + priceFloorFetcher: &mockPriceFloorFetcher{}, } } -type mockBidIDGenerator struct { +type fakeBidIDGenerator struct { GenerateBidID bool `json:"generateBidID"` ReturnError bool `json:"returnError"` + bidCount map[string]int } -func (big *mockBidIDGenerator) Enabled() bool { - return big.GenerateBidID +func (f *fakeBidIDGenerator) Enabled() bool { + return f.GenerateBidID } -func (big *mockBidIDGenerator) New() (string, error) { +func (f *fakeBidIDGenerator) New(bidder string) (string, error) { + if f.ReturnError { + return "", errors.New("Test error generating bid.ext.prebid.bidid") + } - if big.ReturnError { - err := errors.New("Test error generating bid.ext.prebid.bidid") - return "", err + if f.bidCount == nil { + f.bidCount = make(map[string]int) } - return "mock_uuid", nil + f.bidCount[bidder] += 1 + return fmt.Sprintf("bid-%v-%v", bidder, f.bidCount[bidder]), nil } -type fakeRandomDeduplicateBidBooleanGenerator struct { - returnValue bool +type fakeBooleanGenerator struct { + value bool } -func (m *fakeRandomDeduplicateBidBooleanGenerator) Generate() bool { - return m.returnValue +func (f *fakeBooleanGenerator) Generate() bool { + return f.value } func newExtRequest() openrtb_ext.ExtRequest { @@ -2747,10 +2594,10 @@ func TestCategoryMapping(t *testing.T) { bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1} - bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", ""} - bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, nil, 0, false, "", 30.0000, "USD", ""} - bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 40.0000, "USD", ""} + bid1_1 := entities.PbsOrtbBid{Bid: &bid1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_2 := entities.PbsOrtbBid{Bid: &bid2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 20.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_3 := entities.PbsOrtbBid{Bid: &bid3, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 30.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_4 := entities.PbsOrtbBid{Bid: &bid4, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 40.0000, OriginalBidCur: "USD", TargetBidderCode: ""} innerBids := []*entities.PbsOrtbBid{ &bid1_1, @@ -2764,7 +2611,7 @@ func TestCategoryMapping(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &SeatNonBidBuilder{}) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message") @@ -2802,10 +2649,10 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1} - bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", ""} - bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, nil, 0, false, "", 30.0000, "USD", ""} - bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, nil, 0, false, "", 40.0000, "USD", ""} + bid1_1 := entities.PbsOrtbBid{Bid: &bid1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_2 := entities.PbsOrtbBid{Bid: &bid2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 20.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_3 := entities.PbsOrtbBid{Bid: &bid3, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 30.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_4 := entities.PbsOrtbBid{Bid: &bid4, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 40.0000, OriginalBidCur: "USD", TargetBidderCode: ""} innerBids := []*entities.PbsOrtbBid{ &bid1_1, @@ -2819,7 +2666,7 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &SeatNonBidBuilder{}) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be no bid rejection messages") @@ -2856,9 +2703,9 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} - bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", ""} - bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 30.0000, "USD", ""} + bid1_1 := entities.PbsOrtbBid{Bid: &bid1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_2 := entities.PbsOrtbBid{Bid: &bid2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 20.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_3 := entities.PbsOrtbBid{Bid: &bid3, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 30.0000, OriginalBidCur: "USD", TargetBidderCode: ""} innerBids := []*entities.PbsOrtbBid{ &bid1_1, @@ -2871,7 +2718,7 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &SeatNonBidBuilder{}) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message") @@ -2938,9 +2785,9 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} - bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", ""} - bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 30.0000, "USD", ""} + bid1_1 := entities.PbsOrtbBid{Bid: &bid1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_2 := entities.PbsOrtbBid{Bid: &bid2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 20.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_3 := entities.PbsOrtbBid{Bid: &bid3, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 30.0000, OriginalBidCur: "USD", TargetBidderCode: ""} innerBids := []*entities.PbsOrtbBid{ &bid1_1, @@ -2953,7 +2800,7 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &SeatNonBidBuilder{}) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be no bid rejection messages") @@ -2965,83 +2812,80 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { } func TestCategoryDedupe(t *testing.T) { - categoriesFetcher, error := newCategoryFetcher("./test/category-mapping") if error != nil { t.Errorf("Failed to create a category Fetcher: %v", error) } - requestExt := newExtRequest() - targData := &targetData{ priceGranularity: *requestExt.Prebid.Targeting.PriceGranularity, includeWinners: true, } - adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) + // bid3 and bid5 will be same price, category, and duration so one of them should be removed based on the dedupe generator + bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-3"}, W: 1, H: 1} + bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 15.0000, Cat: []string{"IAB1-4"}, W: 1, H: 1} + bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 20.0000, Cat: []string{"IAB1-3"}, W: 1, H: 1} + bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: []string{"IAB1-INVALID"}, W: 1, H: 1} + bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 20.0000, Cat: []string{"IAB1-3"}, W: 1, H: 1} - cats1 := []string{"IAB1-3"} - cats2 := []string{"IAB1-4"} - // bid3 will be same price, category, and duration as bid1 so one of them should get removed - cats4 := []string{"IAB1-2000"} - bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 15.0000, Cat: cats2, W: 1, H: 1} - bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 20.0000, Cat: cats1, W: 1, H: 1} - bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1} - bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 20.0000, Cat: cats1, W: 1, H: 1} + bid1_1 := entities.PbsOrtbBid{Bid: &bid1, BidType: "video", BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, OriginalBidCPM: 10.0000, OriginalBidCur: "USD"} + bid1_2 := entities.PbsOrtbBid{Bid: &bid2, BidType: "video", BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, OriginalBidCPM: 15.0000, OriginalBidCur: "USD"} + bid1_3 := entities.PbsOrtbBid{Bid: &bid3, BidType: "video", BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, OriginalBidCPM: 20.0000, OriginalBidCur: "USD"} + bid1_4 := entities.PbsOrtbBid{Bid: &bid4, BidType: "video", BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, OriginalBidCPM: 20.0000, OriginalBidCur: "USD"} + bid1_5 := entities.PbsOrtbBid{Bid: &bid5, BidType: "video", BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, OriginalBidCPM: 20.0000, OriginalBidCur: "USD"} - bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, nil, 0, false, "", 15.0000, "USD", ""} - bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""} - bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""} - bid1_5 := entities.PbsOrtbBid{&bid5, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""} + bidderName1 := openrtb_ext.BidderName("appnexus") - selectedBids := make(map[string]int) - expectedCategories := map[string]string{ - "bid_id1": "10.00_Electronics_30s", - "bid_id2": "14.00_Sports_50s", - "bid_id3": "20.00_Electronics_30s", - "bid_id5": "20.00_Electronics_30s", + tests := []struct { + name string + dedupeGeneratorValue bool + expectedBids []*entities.PbsOrtbBid + expectedCategories map[string]string + }{ + { + name: "bid_id5_selected_over_bid_id3", + dedupeGeneratorValue: true, + expectedBids: []*entities.PbsOrtbBid{&bid1_2, &bid1_5}, + expectedCategories: map[string]string{ + "bid_id2": "14.00_Sports_50s", + "bid_id5": "20.00_Electronics_30s", + }, + }, + { + name: "bid_id3_selected_over_bid_id5", + dedupeGeneratorValue: false, + expectedBids: []*entities.PbsOrtbBid{&bid1_2, &bid1_3}, + expectedCategories: map[string]string{ + "bid_id2": "14.00_Sports_50s", + "bid_id3": "20.00_Electronics_30s", + }, + }, } - numIterations := 10 - - // Run the function many times, this should be enough for the 50% chance of which bid to remove to remove bid1 sometimes - // and bid3 others. It's conceivably possible (but highly unlikely) that the same bid get chosen every single time, but - // if you notice false fails from this test increase numIterations to make it even less likely to happen. - for i := 0; i < numIterations; i++ { - innerBids := []*entities.PbsOrtbBid{ - &bid1_1, - &bid1_2, - &bid1_3, - &bid1_4, - &bid1_5, - } - - seatBid := entities.PbsOrtbSeatBid{Bids: innerBids, Currency: "USD"} - bidderName1 := openrtb_ext.BidderName("appnexus") - - adapterBids[bidderName1] = &seatBid - - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) - - assert.Equal(t, nil, err, "Category mapping error should be empty") - assert.Equal(t, 3, len(rejections), "There should be 2 bid rejection messages") - assert.Regexpf(t, regexp.MustCompile(`bid rejected \[bid ID: bid_id(1|3)\] reason: Bid was deduplicated`), rejections[0], "Rejection message did not match expected") - assert.Equal(t, "bid rejected [bid ID: bid_id4] reason: Category mapping file for primary ad server: 'freewheel', publisher: '' not found", rejections[1], "Rejection message did not match expected") - assert.Equal(t, 2, len(adapterBids[bidderName1].Bids), "Bidders number doesn't match") - assert.Equal(t, 2, len(bidCategory), "Bidders category mapping doesn't match") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + adapterBids := map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + bidderName1: { + Bids: []*entities.PbsOrtbBid{ + &bid1_1, + &bid1_2, + &bid1_3, + &bid1_4, + &bid1_5, + }, + Currency: "USD", + }, + } + deduplicateGenerator := fakeBooleanGenerator{value: tt.dedupeGeneratorValue} + bidCategory, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &deduplicateGenerator, &SeatNonBidBuilder{}) - for bidId, bidCat := range bidCategory { - assert.Equal(t, expectedCategories[bidId], bidCat, "Category mapping doesn't match") - selectedBids[bidId]++ - } + assert.Nil(t, err) + assert.Equal(t, 3, len(rejections)) + assert.Equal(t, adapterBids[bidderName1].Bids, tt.expectedBids) + assert.Equal(t, bidCategory, tt.expectedCategories) + }) } - - assert.Equal(t, numIterations, selectedBids["bid_id2"], "Bid 2 did not make it through every time") - assert.Equal(t, 0, selectedBids["bid_id1"], "Bid 1 should be rejected on every iteration due to lower price") - assert.NotEqual(t, 0, selectedBids["bid_id3"], "Bid 3 should be accepted at least once") - assert.NotEqual(t, 0, selectedBids["bid_id5"], "Bid 5 should be accepted at least once") } func TestNoCategoryDedupe(t *testing.T) { @@ -3069,11 +2913,11 @@ func TestNoCategoryDedupe(t *testing.T) { bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1} bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 14.0000, "USD", ""} - bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 14.0000, "USD", ""} - bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""} - bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""} - bid1_5 := entities.PbsOrtbBid{&bid5, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} + bid1_1 := entities.PbsOrtbBid{Bid: &bid1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 14.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_2 := entities.PbsOrtbBid{Bid: &bid2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 14.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_3 := entities.PbsOrtbBid{Bid: &bid3, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 20.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_4 := entities.PbsOrtbBid{Bid: &bid4, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 20.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_5 := entities.PbsOrtbBid{Bid: &bid5, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""} selectedBids := make(map[string]int) expectedCategories := map[string]string{ @@ -3103,7 +2947,7 @@ func TestNoCategoryDedupe(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &SeatNonBidBuilder{}) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 2, len(rejections), "There should be 2 bid rejection messages") @@ -3149,8 +2993,8 @@ func TestCategoryMappingBidderName(t *testing.T) { bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 10.0000, Cat: cats2, W: 1, H: 1} - bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} + bid1_1 := entities.PbsOrtbBid{Bid: &bid1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_2 := entities.PbsOrtbBid{Bid: &bid2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""} innerBids1 := []*entities.PbsOrtbBid{ &bid1_1, @@ -3168,7 +3012,7 @@ func TestCategoryMappingBidderName(t *testing.T) { adapterBids[bidderName1] = &seatBid1 adapterBids[bidderName2] = &seatBid2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &SeatNonBidBuilder{}) assert.NoError(t, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be 0 bid rejection messages") @@ -3203,8 +3047,8 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) { bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 12.0000, Cat: cats2, W: 1, H: 1} - bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 17}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 8}, nil, nil, 0, false, "", 12.0000, "USD", ""} + bid1_1 := entities.PbsOrtbBid{Bid: &bid1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 17}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_2 := entities.PbsOrtbBid{Bid: &bid2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 8}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 12.0000, OriginalBidCur: "USD", TargetBidderCode: ""} innerBids1 := []*entities.PbsOrtbBid{ &bid1_1, @@ -3222,7 +3066,7 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) { adapterBids[bidderName1] = &seatBid1 adapterBids[bidderName2] = &seatBid2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &SeatNonBidBuilder{}) assert.NoError(t, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be 0 bid rejection messages") @@ -3315,7 +3159,7 @@ func TestBidRejectionErrors(t *testing.T) { innerBids := []*entities.PbsOrtbBid{} for _, bid := range test.bids { currentBid := entities.PbsOrtbBid{ - bid, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, nil, nil, 0, false, "", 10.0000, "USD", ""} + 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: ""} innerBids = append(innerBids, ¤tBid) } @@ -3323,7 +3167,7 @@ func TestBidRejectionErrors(t *testing.T) { adapterBids[bidderName] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *test.reqExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *test.reqExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &SeatNonBidBuilder{}) if len(test.expectedCatDur) > 0 { // Bid deduplication case @@ -3363,8 +3207,8 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { bidApn1 := openrtb2.Bid{ID: "bid_idApn1", ImpID: "imp_idApn1", Price: 10.0000, Cat: cats1, W: 1, H: 1} bidApn2 := openrtb2.Bid{ID: "bid_idApn2", ImpID: "imp_idApn2", Price: 10.0000, Cat: cats2, W: 1, H: 1} - bid1_Apn1 := entities.PbsOrtbBid{&bidApn1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_Apn2 := entities.PbsOrtbBid{&bidApn2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} + bid1_Apn1 := entities.PbsOrtbBid{Bid: &bidApn1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_Apn2 := entities.PbsOrtbBid{Bid: &bidApn2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""} innerBidsApn1 := []*entities.PbsOrtbBid{ &bid1_Apn1, @@ -3386,7 +3230,7 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { adapterBids[bidderNameApn1] = &seatBidApn1 adapterBids[bidderNameApn2] = &seatBidApn2 - bidCategory, _, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{}) + bidCategory, _, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &SeatNonBidBuilder{}) assert.NoError(t, err, "Category mapping error should be empty") assert.Len(t, rejections, 1, "There should be 1 bid rejection message") @@ -3443,11 +3287,11 @@ func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T) bidApn2_1 := openrtb2.Bid{ID: "bid_idApn2_1", ImpID: "imp_idApn2_1", Price: 10.0000, Cat: cats2, W: 1, H: 1} bidApn2_2 := openrtb2.Bid{ID: "bid_idApn2_2", ImpID: "imp_idApn2_2", Price: 20.0000, Cat: cats2, W: 1, H: 1} - bid1_Apn1_1 := entities.PbsOrtbBid{&bidApn1_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_Apn1_2 := entities.PbsOrtbBid{&bidApn1_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""} + bid1_Apn1_1 := entities.PbsOrtbBid{Bid: &bidApn1_1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_Apn1_2 := entities.PbsOrtbBid{Bid: &bidApn1_2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 20.0000, OriginalBidCur: "USD", TargetBidderCode: ""} - bid1_Apn2_1 := entities.PbsOrtbBid{&bidApn2_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_Apn2_2 := entities.PbsOrtbBid{&bidApn2_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""} + bid1_Apn2_1 := entities.PbsOrtbBid{Bid: &bidApn2_1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_Apn2_2 := entities.PbsOrtbBid{Bid: &bidApn2_2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 20.0000, OriginalBidCur: "USD", TargetBidderCode: ""} innerBidsApn1 := []*entities.PbsOrtbBid{ &bid1_Apn1_1, @@ -3470,7 +3314,7 @@ func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T) adapterBids[bidderNameApn1] = &seatBidApn1 adapterBids[bidderNameApn2] = &seatBidApn2 - _, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &fakeRandomDeduplicateBidBooleanGenerator{true}, &nonBids{}) + _, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &fakeBooleanGenerator{value: true}, &SeatNonBidBuilder{}) assert.NoError(t, err, "Category mapping error should be empty") @@ -3525,9 +3369,9 @@ func TestRemoveBidById(t *testing.T) { bidApn1_2 := openrtb2.Bid{ID: "bid_idApn1_2", ImpID: "imp_idApn1_2", Price: 20.0000, Cat: cats1, W: 1, H: 1} bidApn1_3 := openrtb2.Bid{ID: "bid_idApn1_3", ImpID: "imp_idApn1_3", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid1_Apn1_1 := entities.PbsOrtbBid{&bidApn1_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} - bid1_Apn1_2 := entities.PbsOrtbBid{&bidApn1_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""} - bid1_Apn1_3 := entities.PbsOrtbBid{&bidApn1_3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""} + bid1_Apn1_1 := entities.PbsOrtbBid{Bid: &bidApn1_1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_Apn1_2 := entities.PbsOrtbBid{Bid: &bidApn1_2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 20.0000, OriginalBidCur: "USD", TargetBidderCode: ""} + bid1_Apn1_3 := entities.PbsOrtbBid{Bid: &bidApn1_3, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""} type aTest struct { desc string @@ -3585,92 +3429,168 @@ func TestUpdateRejections(t *testing.T) { } func TestApplyDealSupport(t *testing.T) { + type testInput struct { + dealPriority int + impExt json.RawMessage + targ map[string]string + bidderName openrtb_ext.BidderName + } + + type testOutput struct { + hbPbCatDur string + dealErr string + dealTierSatisfied bool + } + testCases := []struct { - description string - dealPriority int - impExt json.RawMessage - targ map[string]string - expectedHbPbCatDur string - expectedDealErr string - expectedDealTierSatisfied bool + description string + in testInput + expected testOutput }{ { - description: "hb_pb_cat_dur should be modified", - dealPriority: 5, - impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`), - targ: map[string]string{ - "hb_pb_cat_dur": "12.00_movies_30s", + description: "hb_pb_cat_dur should be modified", + in: testInput{ + dealPriority: 5, + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_movies_30s", + }, + bidderName: openrtb_ext.BidderName("appnexus"), + }, + expected: testOutput{ + hbPbCatDur: "tier5_movies_30s", + dealErr: "", + dealTierSatisfied: true, }, - expectedHbPbCatDur: "tier5_movies_30s", - expectedDealErr: "", - expectedDealTierSatisfied: true, }, { - description: "hb_pb_cat_dur should not be modified due to priority not exceeding min", - dealPriority: 9, - impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 10, "prefix": "tier"}, "placementId": 10433394}}}}`), - targ: map[string]string{ - "hb_pb_cat_dur": "12.00_medicine_30s", + description: "hb_pb_cat_dur should be modified even with a mixed case bidder in the impExt", + in: testInput{ + dealPriority: 5, + impExt: json.RawMessage(`{"prebid": {"bidder": {"APPnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_movies_30s", + }, + bidderName: openrtb_ext.BidderName("appnexus"), + }, + expected: testOutput{ + hbPbCatDur: "tier5_movies_30s", + dealErr: "", + dealTierSatisfied: true, }, - expectedHbPbCatDur: "12.00_medicine_30s", - expectedDealErr: "", - expectedDealTierSatisfied: false, }, { - description: "hb_pb_cat_dur should not be modified due to invalid config", - dealPriority: 5, - impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": ""}, "placementId": 10433394}}}}`), - targ: map[string]string{ - "hb_pb_cat_dur": "12.00_games_30s", + description: "hb_pb_cat_dur should be modified even with a mixed case bidder in the winningBidsByBidder map", + in: testInput{ + dealPriority: 5, + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_movies_30s", + }, + bidderName: openrtb_ext.BidderName("APPnexus"), + }, + expected: testOutput{ + hbPbCatDur: "tier5_movies_30s", + dealErr: "", + dealTierSatisfied: true, }, - expectedHbPbCatDur: "12.00_games_30s", - expectedDealErr: "dealTier configuration invalid for bidder 'appnexus', imp ID 'imp_id1'", - expectedDealTierSatisfied: false, }, { - description: "hb_pb_cat_dur should not be modified due to deal priority of 0", - dealPriority: 0, - impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`), - targ: map[string]string{ - "hb_pb_cat_dur": "12.00_auto_30s", - }, - expectedHbPbCatDur: "12.00_auto_30s", - expectedDealErr: "", - expectedDealTierSatisfied: false, + description: "hb_pb_cat_dur should not be modified due to unknown bidder in the winningBidsByBidder map", + in: testInput{ + dealPriority: 9, + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 10, "prefix": "tier"}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_medicine_30s", + }, + bidderName: openrtb_ext.BidderName("unknown"), + }, + expected: testOutput{ + hbPbCatDur: "12.00_medicine_30s", + dealErr: "", + dealTierSatisfied: false, + }, + }, + { + description: "hb_pb_cat_dur should not be modified due to priority not exceeding min", + in: testInput{ + dealPriority: 9, + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 10, "prefix": "tier"}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_medicine_30s", + }, + bidderName: openrtb_ext.BidderName("appnexus"), + }, + expected: testOutput{ + hbPbCatDur: "12.00_medicine_30s", + dealErr: "", + dealTierSatisfied: false, + }, + }, + { + description: "hb_pb_cat_dur should not be modified due to invalid config", + in: testInput{ + dealPriority: 5, + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": ""}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_games_30s", + }, + bidderName: openrtb_ext.BidderName("appnexus"), + }, + expected: testOutput{ + hbPbCatDur: "12.00_games_30s", + dealErr: "dealTier configuration invalid for bidder 'appnexus', imp ID 'imp_id1'", + dealTierSatisfied: false, + }, + }, + { + description: "hb_pb_cat_dur should not be modified due to deal priority of 0", + in: testInput{ + dealPriority: 0, + impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`), + targ: map[string]string{ + "hb_pb_cat_dur": "12.00_auto_30s", + }, + bidderName: openrtb_ext.BidderName("appnexus"), + }, + expected: testOutput{ + hbPbCatDur: "12.00_auto_30s", + dealErr: "", + dealTierSatisfied: false, + }, }, } - bidderName := openrtb_ext.BidderName("appnexus") for _, test := range testCases { bidRequest := &openrtb2.BidRequest{ ID: "some-request-id", Imp: []openrtb2.Imp{ { ID: "imp_id1", - Ext: test.impExt, + Ext: test.in.impExt, }, }, } - bid := entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, test.dealPriority, false, "", 0, "USD", ""} + bid := entities.PbsOrtbBid{Bid: &openrtb2.Bid{ID: "123456"}, BidMeta: nil, BidType: "video", BidTargets: map[string]string{}, BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, BidEvents: nil, BidFloors: nil, DealPriority: test.in.dealPriority, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 0, OriginalBidCur: "USD", TargetBidderCode: ""} bidCategory := map[string]string{ - bid.Bid.ID: test.targ["hb_pb_cat_dur"], + bid.Bid.ID: test.in.targ["hb_pb_cat_dur"], } auc := &auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp_id1": { - bidderName: {&bid}, + test.in.bidderName: {&bid}, }, }, } dealErrs := applyDealSupport(bidRequest, auc, bidCategory, nil) - assert.Equal(t, test.expectedHbPbCatDur, bidCategory[auc.winningBidsByBidder["imp_id1"][bidderName][0].Bid.ID], test.description) - assert.Equal(t, test.expectedDealTierSatisfied, auc.winningBidsByBidder["imp_id1"][bidderName][0].DealTierSatisfied, "expectedDealTierSatisfied=%v when %v", test.expectedDealTierSatisfied, test.description) - if len(test.expectedDealErr) > 0 { - assert.Containsf(t, dealErrs, errors.New(test.expectedDealErr), "Expected error message not found in deal errors") + assert.Equal(t, test.expected.hbPbCatDur, bidCategory[auc.allBidsByBidder["imp_id1"][test.in.bidderName][0].Bid.ID], test.description) + assert.Equal(t, test.expected.dealTierSatisfied, auc.allBidsByBidder["imp_id1"][test.in.bidderName][0].DealTierSatisfied, "expected.dealTierSatisfied=%v when %v", test.expected.dealTierSatisfied, test.description) + if len(test.expected.dealErr) > 0 { + assert.Containsf(t, dealErrs, errors.New(test.expected.dealErr), "Expected error message not found in deal errors") } } } @@ -3709,11 +3629,11 @@ func TestApplyDealSupportMultiBid(t *testing.T) { }, }, auc: &auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp_id1": { openrtb_ext.BidderName("appnexus"): { - &entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""}, - &entities.PbsOrtbBid{&openrtb2.Bid{ID: "789101"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""}, + &entities.PbsOrtbBid{Bid: &openrtb2.Bid{ID: "123456"}, BidMeta: nil, BidType: "video", BidTargets: map[string]string{}, BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, BidEvents: nil, BidFloors: nil, DealPriority: 5, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 0, OriginalBidCur: "USD", TargetBidderCode: ""}, + &entities.PbsOrtbBid{Bid: &openrtb2.Bid{ID: "789101"}, BidMeta: nil, BidType: "video", BidTargets: map[string]string{}, BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, BidEvents: nil, BidFloors: nil, DealPriority: 5, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 0, OriginalBidCur: "USD", TargetBidderCode: ""}, }, }, }, @@ -3755,11 +3675,11 @@ func TestApplyDealSupportMultiBid(t *testing.T) { }, }, auc: &auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp_id1": { openrtb_ext.BidderName("appnexus"): { - &entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""}, - &entities.PbsOrtbBid{&openrtb2.Bid{ID: "789101"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""}, + &entities.PbsOrtbBid{Bid: &openrtb2.Bid{ID: "123456"}, BidMeta: nil, BidType: "video", BidTargets: map[string]string{}, BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, BidEvents: nil, BidFloors: nil, DealPriority: 5, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 0, OriginalBidCur: "USD", TargetBidderCode: ""}, + &entities.PbsOrtbBid{Bid: &openrtb2.Bid{ID: "789101"}, BidMeta: nil, BidType: "video", BidTargets: map[string]string{}, BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, BidEvents: nil, BidFloors: nil, DealPriority: 5, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 0, OriginalBidCur: "USD", TargetBidderCode: ""}, }, }, }, @@ -3806,11 +3726,11 @@ func TestApplyDealSupportMultiBid(t *testing.T) { }, }, auc: &auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "imp_id1": { openrtb_ext.BidderName("appnexus"): { - &entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""}, - &entities.PbsOrtbBid{&openrtb2.Bid{ID: "789101"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""}, + &entities.PbsOrtbBid{Bid: &openrtb2.Bid{ID: "123456"}, BidMeta: nil, BidType: "video", BidTargets: map[string]string{}, BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, BidEvents: nil, BidFloors: nil, DealPriority: 5, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 0, OriginalBidCur: "USD", TargetBidderCode: ""}, + &entities.PbsOrtbBid{Bid: &openrtb2.Bid{ID: "789101"}, BidMeta: nil, BidType: "video", BidTargets: map[string]string{}, BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, BidEvents: nil, BidFloors: nil, DealPriority: 5, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 0, OriginalBidCur: "USD", TargetBidderCode: ""}, }, }, }, @@ -3845,7 +3765,7 @@ func TestApplyDealSupportMultiBid(t *testing.T) { errs := applyDealSupport(tt.args.bidRequest, tt.args.auc, tt.args.bidCategory, tt.args.multiBid) assert.Equal(t, tt.want.errs, errs) - for impID, topBidsPerImp := range tt.args.auc.winningBidsByBidder { + for impID, topBidsPerImp := range tt.args.auc.allBidsByBidder { for bidder, topBidsPerBidder := range topBidsPerImp { for i, topBid := range topBidsPerBidder { assert.Equal(t, tt.want.expectedHbPbCatDur[impID][bidder.String()][i], tt.args.bidCategory[topBid.Bid.ID], tt.name) @@ -4001,7 +3921,7 @@ func TestUpdateHbPbCatDur(t *testing.T) { } for _, test := range testCases { - bid := entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, test.dealPriority, false, "", 0, "USD", ""} + bid := entities.PbsOrtbBid{Bid: &openrtb2.Bid{ID: "123456"}, BidMeta: nil, BidType: "video", BidTargets: map[string]string{}, BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, BidEvents: nil, BidFloors: nil, DealPriority: test.dealPriority, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 0, OriginalBidCur: "USD", TargetBidderCode: ""} bidCategory := map[string]string{ bid.Bid.ID: test.targ["hb_pb_cat_dur"], } @@ -4034,7 +3954,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, origbidcpm: 10.0000, origbidcur: "USD", - expectedBidExt: `{"prebid":{"meta": {"brandName": "foo"}, "passthrough":{"imp_passthrough_val":1}, "type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta": {"brandName": "foo","adaptercode": "adapter"}, "passthrough":{"imp_passthrough_val":1}, "type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4044,7 +3964,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), nil}}, origbidcpm: 10.0000, origbidcur: "USD", - expectedBidExt: `{"prebid":{"meta": {"brandName": "foo"}, "type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta": {"brandName": "foo","adaptercode": "adapter"}, "type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4053,7 +3973,7 @@ func TestMakeBidExtJSON(t *testing.T) { extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")}, impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, origbidcpm: 0, - expectedBidExt: `{"origbidcpm": 0,"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}`, + expectedBidExt: `{"origbidcpm": 0,"prebid":{"meta":{"adaptercode": "adapter"},"passthrough":{"imp_passthrough_val":1}, "type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}`, expectedErrMessage: "", }, { @@ -4063,7 +3983,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: map[string]ImpExtInfo{"another_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, origbidcpm: 10.0000, origbidcur: "USD", - expectedBidExt: `{"prebid":{"type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"adaptercode": "adapter"},"type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4073,7 +3993,7 @@ func TestMakeBidExtJSON(t *testing.T) { origbidcpm: 10.0000, origbidcur: "USD", impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, - expectedBidExt: `{"prebid":{"passthrough":{"imp_passthrough_val":1}},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"adaptercode": "adapter"},"passthrough":{"imp_passthrough_val":1}},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4083,7 +4003,7 @@ func TestMakeBidExtJSON(t *testing.T) { origbidcpm: 10.0000, origbidcur: "USD", impExtInfo: nil, - expectedBidExt: `{"prebid":{"type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"adaptercode": "adapter"},"type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4093,7 +4013,7 @@ func TestMakeBidExtJSON(t *testing.T) { origbidcpm: 10.0000, origbidcur: "USD", impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"banner":{"h":480}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, - expectedBidExt: `{"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"adaptercode": "adapter"},"passthrough":{"imp_passthrough_val":1}, "type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4103,7 +4023,7 @@ func TestMakeBidExtJSON(t *testing.T) { origbidcpm: 10.0000, origbidcur: "USD", impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"banner":{"h":480}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, - expectedBidExt: `{"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":"video"}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"adaptercode": "adapter"},"passthrough":{"imp_passthrough_val":1}, "type":"video"}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4113,7 +4033,7 @@ func TestMakeBidExtJSON(t *testing.T) { origbidcpm: 10.0000, origbidcur: "USD", impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}}, - expectedBidExt: `{"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"adaptercode": "adapter"},"passthrough":{"imp_passthrough_val":1}, "type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4123,7 +4043,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: map[string]ImpExtInfo{}, origbidcpm: 0, origbidcur: "USD", - expectedBidExt: `{"origbidcpm": 0,"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcur": "USD"}`, + expectedBidExt: `{"origbidcpm": 0,"prebid":{"meta":{"brandName":"foo","adaptercode": "adapter"},"type":"banner"}, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4133,7 +4053,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: nil, origbidcpm: 0, origbidcur: "USD", - expectedBidExt: `{"origbidcpm": 0,"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcur": "USD"}`, + expectedBidExt: `{"origbidcpm": 0,"prebid":{"meta":{"brandName":"foo","adaptercode": "adapter"},"type":"banner"}, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4143,7 +4063,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: nil, origbidcpm: 10.0000, origbidcur: "USD", - expectedBidExt: `{"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"brandName":"foo","adaptercode": "adapter"},"type":"banner"}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4153,7 +4073,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: nil, origbidcpm: 10.0000, origbidcur: "USD", - expectedBidExt: `{"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcpm": 10, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"brandName":"foo","adaptercode": "adapter"},"type":"banner"}, "origbidcpm": 10, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4163,7 +4083,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: nil, origbidcpm: -1, origbidcur: "USD", - expectedBidExt: `{"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcur": "USD"}`, + expectedBidExt: `{"prebid":{"meta":{"brandName":"foo","adaptercode":"adapter"},"type":"banner"}, "origbidcur": "USD"}`, expectedErrMessage: "", }, { @@ -4173,7 +4093,7 @@ func TestMakeBidExtJSON(t *testing.T) { impExtInfo: nil, origbidcpm: 0, origbidcur: "USD", - expectedBidExt: `{"origbidcpm": 0,"prebid":{"type":"banner"}, "origbidcur": "USD"}`, + expectedBidExt: `{"origbidcpm": 0,"prebid":{"type":"banner","meta":{"adaptercode":"adapter"}}, "origbidcur": "USD"}`, expectedErrMessage: "", }, //Error cases @@ -4189,13 +4109,14 @@ func TestMakeBidExtJSON(t *testing.T) { ext: json.RawMessage(`{"prebid":{"meta":{"brandId":"foo"}}}`), // brandId should be an int, but is a string in this test case extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("banner")}, impExtInfo: nil, - expectedErrMessage: "error validaing response from server, cannot unmarshal openrtb_ext.ExtBidPrebidMeta.BrandID: unexpected character: \xff", + expectedErrMessage: "error validating response from server, cannot unmarshal openrtb_ext.ExtBidPrebidMeta.BrandID: unexpected character: \xff", }, } for _, test := range testCases { t.Run(test.description, func(t *testing.T) { - result, err := makeBidExtJSON(test.ext, &test.extBidPrebid, test.impExtInfo, "test_imp_id", test.origbidcpm, test.origbidcur) + var adapter openrtb_ext.BidderName = "adapter" + result, err := makeBidExtJSON(test.ext, &test.extBidPrebid, test.impExtInfo, "test_imp_id", test.origbidcpm, test.origbidcur, adapter) if test.expectedErrMessage == "" { assert.JSONEq(t, test.expectedBidExt, string(result), "Incorrect result") @@ -4217,7 +4138,7 @@ func TestStoredAuctionResponses(t *testing.T) { e.cache = &wellBehavedCache{} e.me = &metricsConf.NilMetricsEngine{} e.categoriesFetcher = categoriesFetcher - e.bidIDGenerator = &mockBidIDGenerator{false, false} + e.bidIDGenerator = &fakeBidIDGenerator{GenerateBidID: false, ReturnError: false} e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) e.gdprPermsBuilder = fakePermissionsBuilder{ permissions: &permissionsMock{ @@ -4230,7 +4151,7 @@ func TestStoredAuctionResponses(t *testing.T) { ID: "request-id", Imp: []openrtb2.Imp{{ ID: "impression-id", - Video: &openrtb2.Video{W: 400, H: 300}, + Video: &openrtb2.Video{W: ptrutil.ToPtr[int64](400), H: ptrutil.ToPtr[int64](300)}, }}, } @@ -4239,7 +4160,7 @@ func TestStoredAuctionResponses(t *testing.T) { SeatBid: []openrtb2.SeatBid{ { Bid: []openrtb2.Bid{ - {ID: "bid_id", ImpID: "impression-id", Ext: json.RawMessage(`{"origbidcpm":0,"prebid":{"type":"video"}}`)}, + {ID: "bid_id", ImpID: "impression-id", Ext: json.RawMessage(`{"origbidcpm":0,"prebid":{"meta":{},"type":"video"}}`)}, }, Seat: "appnexus", }, @@ -4583,7 +4504,7 @@ func TestAuctionDebugEnabled(t *testing.T) { e.cache = &wellBehavedCache{} e.me = &metricsConf.NilMetricsEngine{} e.categoriesFetcher = categoriesFetcher - e.bidIDGenerator = &mockBidIDGenerator{false, false} + e.bidIDGenerator = &fakeBidIDGenerator{GenerateBidID: false, ReturnError: false} e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) e.gdprPermsBuilder = fakePermissionsBuilder{ permissions: &permissionsMock{ @@ -4660,7 +4581,7 @@ func TestPassExperimentConfigsToHoldAuction(t *testing.T) { }, }.Builder - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &signer, macros.NewStringIndexBasedReplacer()).(*exchange) + e := NewExchange(adapters, nil, cfg, &mockRequestValidator{}, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &signer, macros.NewStringIndexBasedReplacer(), nil).(*exchange) // Define mock incoming bid requeset mockBidRequest := &openrtb2.BidRequest{ @@ -4850,48 +4771,115 @@ func TestMakeBidWithValidation(t *testing.T) { sampleAd := "" sampleOpenrtbBid := &openrtb2.Bid{ID: "some-bid-id", AdM: sampleAd} - // Define test cases testCases := []struct { - description string - givenValidations config.Validations - givenBids []*entities.PbsOrtbBid - expectedNumOfBids int + name string + givenBidRequestExt json.RawMessage + givenValidations config.Validations + givenBids []*entities.PbsOrtbBid + givenSeat openrtb_ext.BidderName + expectedNumOfBids int + expectedNonBids *SeatNonBidBuilder + expectedNumDebugErrors int + expectedNumDebugWarnings int }{ { - description: "Validation is enforced, and one bid out of the two is invalid based on dimensions", + name: "One_of_two_bids_is_invalid_based_on_DSA_object_presence", + givenBidRequestExt: json.RawMessage(`{"dsa": {"dsarequired": 2}}`), + givenValidations: config.Validations{}, + givenBids: []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{Ext: json.RawMessage(`{"dsa": {"adrender":1}}`)}}, {Bid: &openrtb2.Bid{}}}, + givenSeat: "pubmatic", + expectedNumOfBids: 1, + expectedNonBids: &SeatNonBidBuilder{ + "pubmatic": { + { + StatusCode: 300, + Ext: &openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{}, + }, + }, + }, + }, + }, + expectedNumDebugWarnings: 1, + }, + { + name: "Creative_size_validation_enforced,_one_of_two_bids_has_invalid_dimensions", givenValidations: config.Validations{BannerCreativeMaxSize: config.ValidationEnforce, MaxCreativeWidth: 100, MaxCreativeHeight: 100}, 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: &SeatNonBidBuilder{ + "pubmatic": { + { + StatusCode: 351, + Ext: &openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + W: 200, + H: 200, + }, + }, + }, + }, + }, + }, + expectedNumDebugErrors: 1, }, { - description: "Validation is warned, so no bids should be removed (Validating CreativeMaxSize) ", - givenValidations: config.Validations{BannerCreativeMaxSize: config.ValidationWarn, MaxCreativeWidth: 100, MaxCreativeHeight: 100}, - 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}}, - expectedNumOfBids: 2, + name: "Creative_size_validation_warned,_one_of_two_bids_has_invalid_dimensions", + givenValidations: config.Validations{BannerCreativeMaxSize: config.ValidationWarn, MaxCreativeWidth: 100, MaxCreativeHeight: 100}, + 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: &SeatNonBidBuilder{}, + expectedNumDebugErrors: 1, }, { - description: "Validation is enforced, and one bid out of the two is invalid based on AdM", + name: "AdM_validation_enforced,_one_of_two_bids_has_invalid_AdM", givenValidations: config.Validations{SecureMarkup: config.ValidationEnforce}, 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: &SeatNonBidBuilder{ + "pubmatic": { + { + ImpId: "1", + StatusCode: 352, + Ext: &openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{}, + }, + }, + }, + }, + }, + expectedNumDebugErrors: 1, }, { - description: "Validation is warned so no bids should be removed (Validating SecureMarkup)", - givenValidations: config.Validations{SecureMarkup: config.ValidationWarn}, - 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}}, - expectedNumOfBids: 2, + name: "AdM_validation_warned,_one_of_two_bids_has_invalid_AdM", + givenValidations: config.Validations{SecureMarkup: config.ValidationWarn}, + 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: &SeatNonBidBuilder{}, + expectedNumDebugErrors: 1, }, { - description: "Adm validation is skipped, creative size validation is enforced, one Adm is invalid, but because we skip, no bids should be removed", + name: "Adm_validation_skipped,_creative_size_validation_enforced,_one_of_two_bids_has_invalid_AdM", givenValidations: config.Validations{SecureMarkup: config.ValidationSkip, BannerCreativeMaxSize: config.ValidationEnforce}, 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: &SeatNonBidBuilder{}, }, { - description: "Creative Size Validation is skipped, Adm Validation is enforced, one Creative Size is invalid, but because we skip, no bids should be removed", - givenValidations: config.Validations{BannerCreativeMaxSize: config.ValidationWarn, MaxCreativeWidth: 100, MaxCreativeHeight: 100}, + name: "Creative_size_validation_skipped,_Adm_Validation_enforced,_one_of_two_bids_has_invalid_dimensions", + givenValidations: config.Validations{BannerCreativeMaxSize: config.ValidationSkip, MaxCreativeWidth: 100, MaxCreativeHeight: 100}, 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: &SeatNonBidBuilder{}, }, } @@ -4920,20 +4908,35 @@ func TestMakeBidWithValidation(t *testing.T) { e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - bidExtResponse := &openrtb_ext.ExtBidResponse{Errors: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage)} - ImpExtInfoMap := make(map[string]ImpExtInfo) ImpExtInfoMap["1"] = ImpExtInfo{} ImpExtInfoMap["2"] = ImpExtInfo{} //Run tests for _, test := range testCases { - e.bidValidationEnforcement = test.givenValidations - sampleBids := test.givenBids - resultingBids, resultingErrs := e.makeBid(sampleBids, sampleAuction, true, ImpExtInfoMap, bidExtResponse, "", "") - - assert.Equal(t, 0, len(resultingErrs), "%s. Test should not return errors \n", test.description) - assert.Equal(t, test.expectedNumOfBids, len(resultingBids), "%s. Test returns more valid bids than expected\n", test.description) + t.Run(test.name, func(t *testing.T) { + bidExtResponse := &openrtb_ext.ExtBidResponse{ + Errors: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage), + Warnings: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage), + } + bidRequest := &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: test.givenBidRequestExt, + }, + }, + } + e.bidValidationEnforcement = test.givenValidations + sampleBids := test.givenBids + nonBids := &SeatNonBidBuilder{} + resultingBids, resultingErrs := e.makeBid(sampleBids, sampleAuction, true, ImpExtInfoMap, bidRequest, bidExtResponse, test.givenSeat, "", nonBids) + + assert.Equal(t, 0, len(resultingErrs)) + assert.Equal(t, test.expectedNumOfBids, len(resultingBids)) + assert.Equal(t, test.expectedNonBids, nonBids) + assert.Equal(t, test.expectedNumDebugErrors, len(bidExtResponse.Errors)) + assert.Equal(t, test.expectedNumDebugWarnings, len(bidExtResponse.Warnings)) + }) } } @@ -5101,7 +5104,7 @@ func TestOverrideConfigAlternateBidderCodesWithRequestValues(t *testing.T) { }.Builder e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) e.categoriesFetcher = categoriesFetcher - e.bidIDGenerator = &mockBidIDGenerator{false, false} + e.bidIDGenerator = &fakeBidIDGenerator{GenerateBidID: false, ReturnError: false} e.requestSplitter = requestSplitter{ me: e.me, gdprPermsBuilder: e.gdprPermsBuilder, @@ -5158,6 +5161,329 @@ func TestOverrideConfigAlternateBidderCodesWithRequestValues(t *testing.T) { } } +func TestGetAllBids(t *testing.T) { + noBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) } + server := httptest.NewServer(http.HandlerFunc(noBidServer)) + defer server.Close() + + type testIn struct { + bidderRequests []BidderRequest + bidAdjustments map[string]float64 + conversions currency.Conversions + accountDebugAllowed bool + globalPrivacyControlHeader string + headerDebugAllowed bool + alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes + experiment *openrtb_ext.Experiment + hookExecutor hookexecution.StageExecutor + pbsRequestStartTime time.Time + bidAdjustmentRules map[string][]openrtb_ext.Adjustment + tmaxAdjustments *TmaxAdjustmentsPreprocessed + adapterMap map[openrtb_ext.BidderName]AdaptedBidder + } + type testResults struct { + adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid + adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra + extraRespInfo extraAuctionResponseInfo + } + testCases := []struct { + desc string + in testIn + expected testResults + }{ + { + desc: "alternateBidderCodes-config-absent: pubmatic bidder returns bids with 'pubmatic' and 'groupm' seats", + in: testIn{ + bidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidderCoreName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id", + }}, + }, + }, + }, + conversions: ¤cy.ConstantRates{}, + hookExecutor: hookexecution.EmptyHookExecutor{}, + pbsRequestStartTime: time.Now(), + adapterMap: map[openrtb_ext.BidderName]AdaptedBidder{ + openrtb_ext.BidderPubmatic: AdaptBidder(&goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + }, + bidResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + {Bid: &openrtb2.Bid{ID: "1"}, Seat: "pubmatic"}, + {Bid: &openrtb2.Bid{ID: "2"}, Seat: "groupm"}, + }, + }, + }, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderPubmatic, nil, ""), + }, + }, + expected: testResults{ + extraRespInfo: extraAuctionResponseInfo{ + bidsFound: true, + }, + adapterExtra: map[openrtb_ext.BidderName]*seatResponseExtra{ + "pubmatic": { + Warnings: []openrtb_ext.ExtBidderMessage{ + { + Code: errortypes.AlternateBidderCodeWarningCode, + Message: `alternateBidderCodes disabled for "pubmatic", rejecting bids for "groupm"`, + }, + }, + }, + }, + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "1", + }, + OriginalBidCur: "USD", + AdapterCode: openrtb_ext.BidderPubmatic, + }, + }, + Currency: "USD", + Seat: "pubmatic", + HttpCalls: []*openrtb_ext.ExtHttpCall{}, + }, + }, + }, + }, + { + desc: "alternateBidderCodes-enabled: pubmatic bidder returns bids with 'pubmatic' and 'groupm' seats", + in: testIn{ + bidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidderCoreName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id", + }}, + }, + }, + }, + conversions: ¤cy.ConstantRates{}, + alternateBidderCodes: openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "pubmatic": { + Enabled: true, + AllowedBidderCodes: []string{"groupm"}, + }, + }, + }, + hookExecutor: hookexecution.EmptyHookExecutor{}, + pbsRequestStartTime: time.Now(), + adapterMap: map[openrtb_ext.BidderName]AdaptedBidder{ + openrtb_ext.BidderPubmatic: AdaptBidder(&goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + }, + bidResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + {Bid: &openrtb2.Bid{ID: "1"}, Seat: "pubmatic"}, + {Bid: &openrtb2.Bid{ID: "2"}, Seat: "groupm"}, + }, + }, + }, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderPubmatic, nil, ""), + }, + }, + expected: testResults{ + extraRespInfo: extraAuctionResponseInfo{ + bidsFound: true, + }, + adapterExtra: map[openrtb_ext.BidderName]*seatResponseExtra{ + "pubmatic": { + Warnings: []openrtb_ext.ExtBidderMessage{}, + }, + }, + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "pubmatic": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "1", + }, + OriginalBidCur: "USD", + AdapterCode: openrtb_ext.BidderPubmatic, + }, + }, + Currency: "USD", + Seat: "pubmatic", + HttpCalls: []*openrtb_ext.ExtHttpCall{}, + }, + "groupm": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "2", + }, + OriginalBidCur: "USD", + AdapterCode: openrtb_ext.BidderPubmatic, + }, + }, + Currency: "USD", + Seat: "groupm", + HttpCalls: []*openrtb_ext.ExtHttpCall{}, + }, + }, + }, + }, + { + desc: "alternateBidderCodes-enabled: pubmatic bidder returns bids with only 'groupm' seat", + in: testIn{ + bidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidderCoreName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id", + }}, + }, + }, + }, + conversions: ¤cy.ConstantRates{}, + alternateBidderCodes: openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "pubmatic": { + Enabled: true, + AllowedBidderCodes: []string{"groupm"}, + }, + }, + }, + hookExecutor: hookexecution.EmptyHookExecutor{}, + pbsRequestStartTime: time.Now(), + adapterMap: map[openrtb_ext.BidderName]AdaptedBidder{ + openrtb_ext.BidderPubmatic: AdaptBidder(&goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + }, + bidResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + {Bid: &openrtb2.Bid{ID: "2"}, Seat: "groupm"}, + }, + }, + }, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderPubmatic, nil, ""), + }, + }, + expected: testResults{ + extraRespInfo: extraAuctionResponseInfo{ + bidsFound: true, + }, + adapterExtra: map[openrtb_ext.BidderName]*seatResponseExtra{ + "pubmatic": { + Warnings: []openrtb_ext.ExtBidderMessage{}, + }, + }, + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ + "groupm": { + Bids: []*entities.PbsOrtbBid{ + { + Bid: &openrtb2.Bid{ + ID: "2", + }, + OriginalBidCur: "USD", + AdapterCode: openrtb_ext.BidderPubmatic, + }, + }, + Currency: "USD", + Seat: "groupm", + HttpCalls: []*openrtb_ext.ExtHttpCall{}, + }, + }, + }, + }, + { + desc: "bidder responded with empty bid", + in: testIn{ + bidderRequests: []BidderRequest{ + { + BidderName: "pubmatic", + BidderCoreName: "pubmatic", + BidRequest: &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id", + }}, + }, + }, + }, + conversions: ¤cy.ConstantRates{}, + alternateBidderCodes: openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "pubmatic": { + Enabled: true, + AllowedBidderCodes: []string{"groupm"}, + }, + }, + }, + hookExecutor: hookexecution.EmptyHookExecutor{}, + pbsRequestStartTime: time.Now(), + adapterMap: map[openrtb_ext.BidderName]AdaptedBidder{ + openrtb_ext.BidderPubmatic: AdaptBidder(&goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + }, + bidResponse: &adapters.BidderResponse{}, + }, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderPubmatic, nil, ""), + }, + }, + expected: testResults{ + extraRespInfo: extraAuctionResponseInfo{ + bidsFound: false, + }, + adapterExtra: map[openrtb_ext.BidderName]*seatResponseExtra{ + "pubmatic": { + Warnings: []openrtb_ext.ExtBidderMessage{}, + }, + }, + adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{}, + }, + }, + } + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + e := exchange{ + cache: &wellBehavedCache{}, + me: &metricsConf.NilMetricsEngine{}, + gdprPermsBuilder: fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + }, + }.Builder, + adapterMap: test.in.adapterMap, + } + + adapterBids, adapterExtra, extraRespInfo := e.getAllBids(context.Background(), test.in.bidderRequests, test.in.bidAdjustments, + test.in.conversions, test.in.accountDebugAllowed, test.in.globalPrivacyControlHeader, test.in.headerDebugAllowed, test.in.alternateBidderCodes, test.in.experiment, + test.in.hookExecutor, test.in.pbsRequestStartTime, test.in.bidAdjustmentRules, test.in.tmaxAdjustments, false) + + assert.Equalf(t, test.expected.extraRespInfo.bidsFound, extraRespInfo.bidsFound, "extraRespInfo.bidsFound mismatch") + assert.Equalf(t, test.expected.adapterBids, adapterBids, "adapterBids mismatch") + assert.Equalf(t, len(test.expected.adapterExtra), len(adapterExtra), "adapterExtra length mismatch") + for adapter, extra := range test.expected.adapterExtra { + assert.Equalf(t, extra.Warnings, adapterExtra[adapter].Warnings, "adapterExtra.Warnings mismatch for adapter [%s]", adapter) + } + }) + } +} + type MockSigner struct { data string } @@ -5179,17 +5505,19 @@ type exchangeSpec struct { DebugLog *DebugLog `json:"debuglog,omitempty"` EventsEnabled bool `json:"events_enabled,omitempty"` StartTime int64 `json:"start_time_ms,omitempty"` - BidIDGenerator *mockBidIDGenerator `json:"bidIDGenerator,omitempty"` + BidIDGenerator *fakeBidIDGenerator `json:"bidIDGenerator,omitempty"` RequestType *metrics.RequestType `json:"requestType,omitempty"` PassthroughFlag bool `json:"passthrough_flag,omitempty"` HostSChainFlag bool `json:"host_schain_flag,omitempty"` HostConfigBidValidation config.Validations `json:"host_bid_validations"` AccountConfigBidValidation config.Validations `json:"account_bid_validations"` AccountFloorsEnabled bool `json:"account_floors_enabled"` + AccountEnforceDealFloors bool `json:"account_enforce_deal_floors"` FledgeEnabled bool `json:"fledge_enabled,omitempty"` MultiBid *multiBidSpec `json:"multiBid,omitempty"` Server exchangeServer `json:"server,omitempty"` - AccountPrivacy *config.AccountPrivacy `json:"accountPrivacy,omitempty"` + AccountPrivacy config.AccountPrivacy `json:"accountPrivacy,omitempty"` + ORTBVersion map[string]string `json:"ortbversion"` } type multiBidSpec struct { @@ -5244,9 +5572,10 @@ type bidderSeatBid struct { // bidderBid is basically a subset of entities.PbsOrtbBid from exchange/bidder.go. // See the comment on bidderSeatBid for more info. type bidderBid struct { - Bid *openrtb2.Bid `json:"ortbBid,omitempty"` - Type string `json:"bidType,omitempty"` - Meta *openrtb_ext.ExtBidPrebidMeta `json:"bidMeta,omitempty"` + Bid *openrtb2.Bid `json:"ortbBid,omitempty"` + Type string `json:"bidType,omitempty"` + BidVideo *openrtb_ext.ExtBidPrebidVideo `json:"bidVideo,omitempty"` + Meta *openrtb_ext.ExtBidPrebidMeta `json:"bidMeta,omitempty"` } type mockIdFetcher map[string]string @@ -5293,6 +5622,7 @@ func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderR bids[i] = &entities.PbsOrtbBid{ OriginalBidCPM: mockSeatBid.Bids[i].Bid.Price, Bid: mockSeatBid.Bids[i].Bid, + BidVideo: mockSeatBid.Bids[i].BidVideo, BidType: openrtb_ext.BidType(mockSeatBid.Bids[i].Type), BidMeta: mockSeatBid.Bids[i].Meta, } @@ -5458,19 +5788,6 @@ func (nilCategoryFetcher) FetchCategories(ctx context.Context, primaryAdServer, return "", nil } -// fakeCurrencyRatesHttpClient is a simple http client mock returning a constant response body -type fakeCurrencyRatesHttpClient struct { - responseBody string -} - -func (m *fakeCurrencyRatesHttpClient) Do(req *http.Request) (*http.Response, error) { - return &http.Response{ - Status: "200 OK", - StatusCode: http.StatusOK, - Body: io.NopCloser(strings.NewReader(m.responseBody)), - }, nil -} - type mockBidder struct { mock.Mock lastExtraRequestInfo *adapters.ExtraRequestInfo @@ -5488,6 +5805,24 @@ func (m *mockBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequ return args.Get(0).(*adapters.BidderResponse), args.Get(1).([]error) } +func parseRequestAliases(r openrtb2.BidRequest) (map[string]string, error) { + if len(r.Ext) == 0 { + return nil, nil + } + + ext := struct { + Prebid struct { + Aliases map[string]string `json:"aliases"` + } `json:"prebid"` + }{} + + if err := jsonutil.Unmarshal(r.Ext, &ext); err != nil { + return nil, err + } + + return ext.Prebid.Aliases, nil +} + func getInfoFromImp(req *openrtb_ext.RequestWrapper) (json.RawMessage, string, error) { bidRequest := req.BidRequest imp := bidRequest.Imp[0] @@ -5514,13 +5849,22 @@ func TestModulesCanBeExecutedForMultipleBiddersSimultaneously(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(noBidServer)) defer server.Close() - bidderImpl := &goodSingleBidder{ - httpRequest: &adapters.RequestData{ - Method: "POST", - Uri: server.URL, - Body: []byte(`{"key":"val"}`), - Headers: http.Header{}, - }, + reqBdy := []byte(`{"key":"val"}`) + + bidderImplAppnexus := &goodSingleBidder{ + httpRequest: &adapters.RequestData{Method: http.MethodPost, Uri: server.URL, Body: reqBdy, Headers: http.Header{}}, + bidResponse: &adapters.BidderResponse{}, + } + bidderImplTelaria := &goodSingleBidder{ + httpRequest: &adapters.RequestData{Method: http.MethodPost, Uri: server.URL, Body: reqBdy, Headers: http.Header{}}, + bidResponse: &adapters.BidderResponse{}, + } + bidderImpl33Across := &goodSingleBidder{ + httpRequest: &adapters.RequestData{Method: http.MethodPost, Uri: server.URL, Body: reqBdy, Headers: http.Header{}}, + bidResponse: &adapters.BidderResponse{}, + } + bidderImplAax := &goodSingleBidder{ + httpRequest: &adapters.RequestData{Method: http.MethodPost, Uri: server.URL, Body: reqBdy, Headers: http.Header{}}, bidResponse: &adapters.BidderResponse{}, } @@ -5559,10 +5903,10 @@ func TestModulesCanBeExecutedForMultipleBiddersSimultaneously(t *testing.T) { } e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{ - openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, ""), - openrtb_ext.BidderTelaria: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, ""), - openrtb_ext.Bidder33Across: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.Bidder33Across, &config.DebugInfo{}, ""), - openrtb_ext.BidderAax: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAax, &config.DebugInfo{}, ""), + openrtb_ext.BidderAppnexus: AdaptBidder(bidderImplAppnexus, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, ""), + openrtb_ext.BidderTelaria: AdaptBidder(bidderImplTelaria, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderTelaria, &config.DebugInfo{}, ""), + openrtb_ext.Bidder33Across: AdaptBidder(bidderImpl33Across, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.Bidder33Across, &config.DebugInfo{}, ""), + openrtb_ext.BidderAax: AdaptBidder(bidderImplAax, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAax, &config.DebugInfo{}, ""), } // Run test _, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{}) @@ -5620,12 +5964,16 @@ func (e mockUpdateBidRequestHook) HandleBidderRequestHook(_ context.Context, mct c := hookstage.ChangeSet[hookstage.BidderRequestPayload]{} c.AddMutation( func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) { - payload.BidRequest.Site.Name = "test" + site := ptrutil.Clone(payload.Request.Site) + site.Name = "test" + payload.Request.Site = site return payload, nil }, hookstage.MutationUpdate, "bidRequest", "site.name", ).AddMutation( func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) { - payload.BidRequest.Site.Domain = "test.com" + site := ptrutil.Clone(payload.Request.Site) + site.Domain = "test.com" + payload.Request.Site = site return payload, nil }, hookstage.MutationUpdate, "bidRequest", "site.domain", ) @@ -5716,7 +6064,7 @@ func TestSelectNewDuration(t *testing.T) { func TestSetSeatNonBid(t *testing.T) { type args struct { bidResponseExt *openrtb_ext.ExtBidResponse - seatNonBids nonBids + seatNonBids SeatNonBidBuilder } tests := []struct { name string @@ -5725,12 +6073,12 @@ func TestSetSeatNonBid(t *testing.T) { }{ { name: "empty-seatNonBidsMap", - args: args{seatNonBids: nonBids{}, bidResponseExt: nil}, + args: args{seatNonBids: SeatNonBidBuilder{}, bidResponseExt: nil}, want: nil, }, { name: "nil-bidResponseExt", - args: args{seatNonBids: nonBids{seatNonBidsMap: map[string][]openrtb_ext.NonBid{"key": nil}}, bidResponseExt: nil}, + args: args{seatNonBids: SeatNonBidBuilder{"key": nil}, bidResponseExt: nil}, want: &openrtb_ext.ExtBidResponse{ Prebid: &openrtb_ext.ExtResponsePrebid{ SeatNonBid: []openrtb_ext.SeatNonBid{{ @@ -5940,3 +6288,73 @@ 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) + }) + } +} + +type mockRequestValidator struct { + errors []error +} + +func (mrv *mockRequestValidator) ValidateImp(imp *openrtb_ext.ImpWrapper, cfg ortb.ValidationConfig, index int, aliases map[string]string, hasStoredResponses bool, storedBidResponses stored_responses.ImpBidderStoredResp) []error { + return mrv.errors +} diff --git a/exchange/exchangetest/alternate-bidder-codes.json b/exchange/exchangetest/alternate-bidder-codes.json new file mode 100644 index 00000000000..a2272488077 --- /dev/null +++ b/exchange/exchangetest/alternate-bidder-codes.json @@ -0,0 +1,260 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "pubmatic": { + "publisherId": 5890 + }, + "appnexus": { + "placementId": 1 + } + } + } + } + } + ], + "ext": { + "prebid": { + "alternatebiddercodes": { + "enabled": true, + "bidders": { + "PUBmatic": { + "enabled": true, + "allowedbiddercodes": [ + "groupm" + ] + } + } + } + } + } + } + }, + "outgoingRequests": { + "pubmatic": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "publisherId": 5890 + } + } + } + ], + "ext": { + "prebid": { + "alternatebiddercodes": { + "enabled": true, + "bidders": { + "pubmatic": { + "enabled": true, + "allowedbiddercodes": [ + "groupm" + ] + } + } + } + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "pubmatic-bid-1", + "impid": "imp-id-1", + "price": 0.71 + }, + "bidType": "video", + "bidMeta": {} + } + ], + "seat": "pubmatic" + }, + { + "pbsBids": [ + { + "ortbBid": { + "id": "pubmatic-bid-2", + "impid": "imp-id-1", + "price": 0.51 + }, + "bidType": "video", + "bidMeta": {} + } + ], + "seat": "groupm" + } + ] + } + }, + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "ext": { + "prebid": { + "alternatebiddercodes": { + "enabled": true, + "bidders": null + } + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "appnexus-bid-1", + "impid": "imp-id-1", + "price": 0.3 + }, + "bidType": "banner", + "bidMeta": {} + } + ], + "seat": "appnexus" + }, + { + "pbsBids": [ + { + "ortbBid": { + "id": "appnexus-bid-2", + "impid": "imp-id-1", + "price": 0.3 + }, + "bidType": "banner", + "bidMeta": { + } + } + ], + "seat": "groupm" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "groupm", + "bid": [ + { + "id": "pubmatic-bid-2", + "impid": "imp-id-1", + "price": 0.51, + "ext": { + "origbidcpm": 0.51, + "prebid": { + "meta": {}, + "type": "video" + } + } + }, + { + "id": "appnexus-bid-2", + "impid": "imp-id-1", + "price": 0.3, + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": { + }, + "type": "banner" + } + } + } + ] + }, + { + "seat": "pubmatic", + "bid": [ + { + "id": "pubmatic-bid-1", + "impid": "imp-id-1", + "price": 0.71, + "ext": { + "origbidcpm": 0.71, + "prebid": { + "meta": { + }, + "type": "video" + } + } + } + ] + }, + { + "seat": "appnexus", + "bid": [ + { + "id": "appnexus-bid-1", + "impid": "imp-id-1", + "price": 0.3, + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": { + }, + "type": "banner" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/append-bidder-names.json b/exchange/exchangetest/append-bidder-names.json index 659045cd588..0be1fdca522 100644 --- a/exchange/exchangetest/append-bidder-names.json +++ b/exchange/exchangetest/append-bidder-names.json @@ -137,6 +137,8 @@ "origbidcpm": 0.3, "prebid": { "type": "video", + "meta": { + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", @@ -146,10 +148,14 @@ "hb_cache_path_appnex": "/pbcache/endpoint", "hb_pb": "0.20", "hb_pb_appnexus": "0.20", - "hb_pb_cat_dur": "0.20_VideoGames_0s_appnexus", - "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s_appnexus", + "hb_pb_cat_dur": "0.20_VideoGames_30s_appnexus", + "hb_pb_cat_dur_appnex": "0.20_VideoGames_30s_appnexus", "hb_size": "200x250", "hb_size_appnexus": "200x250" + }, + "video": { + "duration": 30, + "primary_category": "" } } } @@ -162,6 +168,8 @@ "ext": { "origbidcpm": 0.6, "prebid": { + "meta": { + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/bid-consolidation-test.json b/exchange/exchangetest/bid-consolidation-test.json index b38e9d69603..5222b6802b0 100644 --- a/exchange/exchangetest/bid-consolidation-test.json +++ b/exchange/exchangetest/bid-consolidation-test.json @@ -127,6 +127,8 @@ "ext": { "origbidcpm": 0.4, "prebid": { + "meta": { + }, "type": "video" } } @@ -146,6 +148,8 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + }, "type": "video" } } @@ -160,6 +164,8 @@ "ext": { "origbidcpm": 0.6, "prebid": { + "meta": { + }, "type": "video" } } diff --git a/exchange/exchangetest/bid-ext-prebid-collision.json b/exchange/exchangetest/bid-ext-prebid-collision.json index 49e4f8a8f22..e38637a4bea 100644 --- a/exchange/exchangetest/bid-ext-prebid-collision.json +++ b/exchange/exchangetest/bid-ext-prebid-collision.json @@ -99,6 +99,8 @@ "someField": "someValue", "origbidcpm": 0.3, "prebid": { + "meta": { + }, "type": "video" } } diff --git a/exchange/exchangetest/bid-ext.json b/exchange/exchangetest/bid-ext.json index 69e7aba9f0c..1d66e7d94f2 100644 --- a/exchange/exchangetest/bid-ext.json +++ b/exchange/exchangetest/bid-ext.json @@ -96,6 +96,8 @@ "origbidcpm": 0.3, "someField": "someValue", "prebid": { + "meta": { + }, "type": "video" } } diff --git a/exchange/exchangetest/bid-id-invalid.json b/exchange/exchangetest/bid-id-invalid.json deleted file mode 100644 index 9c5cb84c310..00000000000 --- a/exchange/exchangetest/bid-id-invalid.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "bidIDGenerator": { - "generateBidID": true, - "returnError": true - }, - "incomingRequest": { - "ortbRequest": { - "id": "some-request-id", - "site": { - "page": "test.somepage.com" - }, - "imp": [ - { - "id": "my-imp-id", - "video": { - "mimes": [ - "video/mp4" - ] - }, - "ext": { - "prebid": { - "bidder": { - "appnexus": { - "placementId": 1 - } - } - } - } - } - ], - "test": 1, - "ext": { - "prebid": { - "targeting": { - "includebrandcategory": { - "primaryadserver": 1, - "publisher": "", - "withcategory": true - }, - "pricegranularity": { - "precision": 2, - "ranges": [ - { - "min": 0, - "max": 20, - "increment": 0.1 - } - ] - }, - "includewinners": true, - "includebidderkeys": true, - "appendbiddernames": true - } - } - } - }, - "usersyncs": { - "appnexus": "123" - } - }, - "outgoingRequests": { - "appnexus": { - "mockResponse": { - "pbsSeatBids": [ - { - "pbsBids": [ - { - "ortbBid": { - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1", - "cat": [ - "IAB1-1" - ] - }, - "bidType": "video", - "bidVideo": { - "duration": 30, - "PrimaryCategory": "" - } - } - ], - "seat": "appnexus" - } - ] - } - } - }, - "response": { - "bids": { - "id": "some-request-id", - "seatbid": [ - { - "seat": "appnexus", - "bid": [ - { - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1", - "cat": [ - "IAB1-1" - ], - "ext": { - "origbidcpm": 0.3, - "prebid": { - "type": "video", - "targeting": { - "hb_bidder": "appnexus", - "hb_bidder_appnexus": "appnexus", - "hb_cache_host": "www.pbcserver.com", - "hb_cache_host_appnex": "www.pbcserver.com", - "hb_cache_path": "/pbcache/endpoint", - "hb_cache_path_appnex": "/pbcache/endpoint", - "hb_pb": "0.20", - "hb_pb_appnexus": "0.20", - "hb_pb_cat_dur": "0.20_VideoGames_0s_appnexus", - "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s_appnexus", - "hb_size": "200x250", - "hb_size_appnexus": "200x250" - } - } - } - } - ] - } - ] - }, - "ext": { - "debug": { - "resolvedrequest": { - "id": "some-request-id", - "imp": [ - { - "id": "my-imp-id", - "video": { - "mimes": [ - "video/mp4" - ] - }, - "ext": { - "prebid": { - "bidder": { - "appnexus": { - "placementId": 1 - } - } - } - } - } - ], - "site": { - "page": "test.somepage.com" - }, - "test": 1, - "ext": { - "prebid": { - "targeting": { - "includebrandcategory": { - "primaryadserver": 1, - "publisher": "", - "withcategory": true - }, - "pricegranularity": { - "precision": 2, - "ranges": [ - { - "min": 0, - "max": 20, - "increment": 0.1 - } - ] - }, - "includewinners": true, - "includebidderkeys": true, - "appendbiddernames": true - } - } - } - } - }, - "errors": { - "prebid": [ - { - "code": 999, - "message": "Error generating bid.ext.prebid.bidid" - } - ] - } - } - } -} \ No newline at end of file diff --git a/exchange/exchangetest/bid-id-valid.json b/exchange/exchangetest/bid-id-valid.json deleted file mode 100644 index b4a30640f77..00000000000 --- a/exchange/exchangetest/bid-id-valid.json +++ /dev/null @@ -1,190 +0,0 @@ -{ - "bidIDGenerator": { - "generateBidID": true, - "returnError": false - }, - "incomingRequest": { - "ortbRequest": { - "id": "some-request-id", - "site": { - "page": "test.somepage.com" - }, - "imp": [ - { - "id": "my-imp-id", - "video": { - "mimes": [ - "video/mp4" - ] - }, - "ext": { - "prebid": { - "bidder": { - "appnexus": { - "placementId": 1 - } - } - } - } - } - ], - "test": 1, - "ext": { - "prebid": { - "targeting": { - "includebrandcategory": { - "primaryadserver": 1, - "publisher": "", - "withcategory": true - }, - "pricegranularity": { - "precision": 2, - "ranges": [ - { - "min": 0, - "max": 20, - "increment": 0.1 - } - ] - }, - "includewinners": true, - "includebidderkeys": true, - "appendbiddernames": true - } - } - } - }, - "usersyncs": { - "appnexus": "123" - } - }, - "outgoingRequests": { - "appnexus": { - "mockResponse": { - "pbsSeatBids": [ - { - "pbsBids": [ - { - "ortbBid": { - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1", - "cat": [ - "IAB1-1" - ] - }, - "bidType": "video", - "bidVideo": { - "duration": 30, - "PrimaryCategory": "" - } - } - ], - "seat": "appnexus" - } - ] - } - } - }, - "response": { - "bids": { - "id": "some-request-id", - "seatbid": [ - { - "seat": "appnexus", - "bid": [ - { - "id": "apn-bid", - "impid": "my-imp-id", - "price": 0.3, - "w": 200, - "h": 250, - "crid": "creative-1", - "cat": [ - "IAB1-1" - ], - "ext": { - "origbidcpm": 0.3, - "prebid": { - "bidid": "mock_uuid", - "type": "video", - "targeting": { - "hb_bidder": "appnexus", - "hb_bidder_appnexus": "appnexus", - "hb_cache_host": "www.pbcserver.com", - "hb_cache_host_appnex": "www.pbcserver.com", - "hb_cache_path": "/pbcache/endpoint", - "hb_cache_path_appnex": "/pbcache/endpoint", - "hb_pb": "0.20", - "hb_pb_appnexus": "0.20", - "hb_pb_cat_dur": "0.20_VideoGames_0s_appnexus", - "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s_appnexus", - "hb_size": "200x250", - "hb_size_appnexus": "200x250" - } - } - } - } - ] - } - ] - }, - "ext": { - "debug": { - "resolvedrequest": { - "id": "some-request-id", - "imp": [ - { - "id": "my-imp-id", - "video": { - "mimes": [ - "video/mp4" - ] - }, - "ext": { - "prebid": { - "bidder": { - "appnexus": { - "placementId": 1 - } - } - } - } - } - ], - "site": { - "page": "test.somepage.com" - }, - "test": 1, - "ext": { - "prebid": { - "targeting": { - "pricegranularity": { - "precision": 2, - "ranges": [ - { - "min": 0, - "max": 20, - "increment": 0.1 - } - ] - }, - "includewinners": true, - "includebidderkeys": true, - "includebrandcategory": { - "primaryadserver": 1, - "publisher": "", - "withcategory": true - }, - "appendbiddernames": true - } - } - } - } - } - } - } -} \ No newline at end of file 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 ea48f3a966f..8c5d22e269b 100644 --- a/exchange/exchangetest/bid_response_validation_enforce_one_bid_rejected.json +++ b/exchange/exchangetest/bid_response_validation_enforce_one_bid_rejected.json @@ -173,6 +173,8 @@ "ext": { "origbidcpm": 0.4, "prebid": { + "meta": { + }, "type": "banner" } } @@ -189,6 +191,30 @@ "message": "bidResponse rejected: size WxH" } ] + }, + "prebid": { + "seatnonbid": [ + { + "nonbid": [ + { + "impid": "some-imp-id", + "statuscode": 351, + "ext": { + "prebid": { + "bid": { + "price": 0.3, + "w": 200, + "h": 500, + "origbidcpm": 0.3 + } + } + } + } + ], + "seat": "appnexus", + "ext": null + } + ] } } } diff --git a/exchange/exchangetest/bid_response_validation_enforce_secure.json b/exchange/exchangetest/bid_response_validation_enforce_secure.json index e036f82a1e3..773f9b9881c 100644 --- a/exchange/exchangetest/bid_response_validation_enforce_secure.json +++ b/exchange/exchangetest/bid_response_validation_enforce_secure.json @@ -183,6 +183,50 @@ } ] } + }, + "prebid": { + "seatnonbid": [ + { + "nonbid": [ + { + "impid": "some-imp-id", + "statuscode": 352, + "ext": { + "prebid": { + "bid": { + "price": 0.3, + "w": 20, + "h": 50, + "origbidcpm": 0.3 + } + } + } + } + ], + "seat": "appnexus", + "ext": null + }, + { + "nonbid": [ + { + "impid": "some-imp-id", + "statuscode": 352, + "ext": { + "prebid": { + "bid": { + "price": 0.4, + "w": 20, + "h": 50, + "origbidcpm": 0.4 + } + } + } + } + ], + "seat": "rubicon", + "ext": null + } + ] } } } \ No newline at end of file diff --git a/exchange/exchangetest/bid_response_validation_warn_creative.json b/exchange/exchangetest/bid_response_validation_warn_creative.json index a170eac778e..8d30c18ccb2 100644 --- a/exchange/exchangetest/bid_response_validation_warn_creative.json +++ b/exchange/exchangetest/bid_response_validation_warn_creative.json @@ -169,6 +169,8 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + }, "type": "banner" } } @@ -188,6 +190,8 @@ "ext": { "origbidcpm": 0.4, "prebid": { + "meta": { + }, "type": "banner" } } diff --git a/exchange/exchangetest/bid_response_validation_warn_secure.json b/exchange/exchangetest/bid_response_validation_warn_secure.json index a6b208d2f0d..9d2af597272 100644 --- a/exchange/exchangetest/bid_response_validation_warn_secure.json +++ b/exchange/exchangetest/bid_response_validation_warn_secure.json @@ -172,6 +172,8 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + }, "type": "banner" } } @@ -192,6 +194,8 @@ "ext": { "origbidcpm": 0.4, "prebid": { + "meta": { + }, "type": "banner" } } diff --git a/exchange/exchangetest/ccpa-featureflag-on.json b/exchange/exchangetest/ccpa-featureflag-on.json index 0c452098da8..c6d37ed91e4 100644 --- a/exchange/exchangetest/ccpa-featureflag-on.json +++ b/exchange/exchangetest/ccpa-featureflag-on.json @@ -1,5 +1,8 @@ { "enforceCcpa": true, + "ortbversion": { + "appnexus":"2.6" + }, "incomingRequest": { "ortbRequest": { "id": "some-request-id", @@ -26,9 +29,7 @@ } ], "regs": { - "ext": { - "us_privacy": "1-Y-" - } + "us_privacy": "1-Y-" }, "user": { "buyeruid": "some-buyer-id" @@ -59,9 +60,7 @@ } ], "regs": { - "ext": { - "us_privacy": "1-Y-" - } + "us_privacy": "1-Y-" }, "user": {} } diff --git a/exchange/exchangetest/debuglog_disabled.json b/exchange/exchangetest/debuglog_disabled.json index d803759d1d9..4b1ad744be4 100644 --- a/exchange/exchangetest/debuglog_disabled.json +++ b/exchange/exchangetest/debuglog_disabled.json @@ -145,6 +145,8 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -155,10 +157,14 @@ "hb_cache_path_appnex": "/pbcache/endpoint", "hb_pb": "0.20", "hb_pb_appnexus": "0.20", - "hb_pb_cat_dur": "0.20_VideoGames_0s", - "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s", + "hb_pb_cat_dur": "0.20_VideoGames_30s", + "hb_pb_cat_dur_appnex": "0.20_VideoGames_30s", "hb_size": "200x250", "hb_size_appnexus": "200x250" + }, + "video": { + "duration": 30, + "primary_category": "" } } } @@ -171,6 +177,8 @@ "ext": { "origbidcpm": 0.6, "prebid": { + "meta": { + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/debuglog_enabled.json b/exchange/exchangetest/debuglog_enabled.json index 054737e653e..faf3834a655 100644 --- a/exchange/exchangetest/debuglog_enabled.json +++ b/exchange/exchangetest/debuglog_enabled.json @@ -147,6 +147,8 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -157,10 +159,14 @@ "hb_cache_path_appnex": "/pbcache/endpoint", "hb_pb": "0.20", "hb_pb_appnexus": "0.20", - "hb_pb_cat_dur": "0.20_VideoGames_0s", - "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s", + "hb_pb_cat_dur": "0.20_VideoGames_30s", + "hb_pb_cat_dur_appnex": "0.20_VideoGames_30s", "hb_size": "200x250", "hb_size_appnexus": "200x250" + }, + "video": { + "duration": 30, + "primary_category": "" } } } @@ -173,6 +179,8 @@ "ext": { "origbidcpm": 0.6, "prebid": { + "meta": { + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/dsa-default-ignored.json b/exchange/exchangetest/dsa-default-ignored.json new file mode 100644 index 00000000000..358bc6d1c25 --- /dev/null +++ b/exchange/exchangetest/dsa-default-ignored.json @@ -0,0 +1,146 @@ +{ + "accountPrivacy": { + "dsa": { + "default": "{\"dsarequired\":2,\"pubrender\":1,\"datatopub\":2,\"transparency\":[{\"domain\":\"platform1domain.com\",\"dsaparams\":[1]},{\"domain\":\"SSP2domain.com\",\"dsaparams\":[1,2]}]}" + } + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } + } + }], + "regs": { + "ext": { + "dsa": { + "dsarequired": 1, + "pubrender": 0, + "datatopub": 2, + "transparency": [{ + "domain": "platform2domain.com", + "dsaparams": [1, 2, 3] + }] + } + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }], + "regs": { + "ext": { + "dsa": { + "dsarequired": 1, + "pubrender": 0, + "datatopub": 2, + "transparency": [{ + "domain": "platform2domain.com", + "dsaparams": [1, 2, 3] + }] + } + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [{ + "pbsBids": [{ + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "origbidcpm": 0.3, + "dsa": { + "behalf": "Advertiser", + "paid": "Advertiser", + "transparency": [{ + "domain": "dsp1domain.com", + "dsaparams": [1, 2] + }], + "adrender": 1 + } + } + }, + "bidType": "video" + }], + "seat": "appnexus" + }] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [{ + "seat": "appnexus", + "bid": [{ + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "origbidcpm": 0.3, + "prebid": { + "meta": { + }, + "type": "video" + }, + "dsa": { + "behalf": "Advertiser", + "paid": "Advertiser", + "transparency": [{ + "domain": "dsp1domain.com", + "dsaparams": [1, 2] + }], + "adrender": 1 + } + } + }] + }] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/dsa-default.json b/exchange/exchangetest/dsa-default.json new file mode 100644 index 00000000000..cd8581e5856 --- /dev/null +++ b/exchange/exchangetest/dsa-default.json @@ -0,0 +1,205 @@ +{ + "accountPrivacy": { + "dsa": { + "default": "{\"dsarequired\":2,\"pubrender\":1,\"datatopub\":2,\"transparency\":[{\"domain\":\"platform1domain.com\",\"dsaparams\":[1]},{\"domain\":\"SSP2domain.com\",\"dsaparams\":[1,2]}]}" + } + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "audienceNetwork": { + "placementId": "some-placement" + } + } + } + } + }] + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }], + "regs": { + "ext": { + "dsa": { + "dsarequired": 2, + "pubrender": 1, + "datatopub": 2, + "transparency": [{ + "domain": "platform1domain.com", + "dsaparams": [1] + }, + { + "domain": "SSP2domain.com", + "dsaparams": [1, 2] + } + ] + } + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [{ + "pbsBids": [{ + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "origbidcpm": 0.3, + "dsa": { + "behalf": "Advertiser", + "paid": "Advertiser", + "transparency": [{ + "domain": "dsp1domain.com", + "dsaparams": [1, 2] + }], + "adrender": 1 + } + } + }, + "bidType": "video" + }], + "seat": "appnexus" + }] + } + }, + "audienceNetwork": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": "some-placement" + } + } + }], + "regs": { + "ext": { + "dsa": { + "dsarequired": 2, + "pubrender": 1, + "datatopub": 2, + "transparency": [{ + "domain": "platform1domain.com", + "dsaparams": [1] + }, + { + "domain": "SSP2domain.com", + "dsaparams": [1, 2] + } + ] + } + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [{ + "pbsBids": [{ + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "origbidcpm": 0.3 + } + }, + "bidType": "video" + }], + "seat": "audienceNetwork" + }] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [{ + "seat": "appnexus", + "bid": [{ + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "origbidcpm": 0.3, + "prebid": { + "meta": { + }, + "type": "video" + }, + "dsa": { + "behalf": "Advertiser", + "paid": "Advertiser", + "transparency": [{ + "domain": "dsp1domain.com", + "dsaparams": [1, 2] + }], + "adrender": 1 + } + } + }] + }, { + "seat": "audienceNetwork", + "bid": [] + }] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/dsa-not-required.json b/exchange/exchangetest/dsa-not-required.json new file mode 100644 index 00000000000..b3f75317eca --- /dev/null +++ b/exchange/exchangetest/dsa-not-required.json @@ -0,0 +1,133 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } + } + }], + "regs": { + "ext": { + "dsa": { + "dsarequired": 1, + "pubrender": 0, + "datatopub": 2, + "transparency": [{ + "domain": "platform1domain.com", + "dsaparams": [1] + }, + { + "domain": "SSP2domain.com", + "dsaparams": [1, 2] + } + ] + } + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }], + "regs": { + "ext": { + "dsa": { + "dsarequired": 1, + "pubrender": 0, + "datatopub": 2, + "transparency": [{ + "domain": "platform1domain.com", + "dsaparams": [1] + }, + { + "domain": "SSP2domain.com", + "dsaparams": [1, 2] + } + ] + } + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [{ + "pbsBids": [{ + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "origbidcpm": 0.3 + } + }, + "bidType": "video" + }], + "seat": "appnexus" + }] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [{ + "seat": "appnexus", + "bid": [{ + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "origbidcpm": 0.3, + "prebid": { + "meta": { + }, + "type": "video" + } + } + }] + }] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/dsa-required.json b/exchange/exchangetest/dsa-required.json new file mode 100644 index 00000000000..a3bccafce75 --- /dev/null +++ b/exchange/exchangetest/dsa-required.json @@ -0,0 +1,218 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "audienceNetwork": { + "placementId": "some-placement" + } + } + } + } + }], + "regs": { + "ext": { + "dsa": { + "dsarequired": 2, + "pubrender": 0, + "datatopub": 2, + "transparency": [{ + "domain": "platform1domain.com", + "dsaparams": [1] + }, + { + "domain": "SSP2domain.com", + "dsaparams": [1, 2] + } + ] + } + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }], + "regs": { + "ext": { + "dsa": { + "dsarequired": 2, + "pubrender": 0, + "datatopub": 2, + "transparency": [{ + "domain": "platform1domain.com", + "dsaparams": [1] + }, + { + "domain": "SSP2domain.com", + "dsaparams": [1, 2] + } + ] + } + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [{ + "pbsBids": [{ + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "origbidcpm": 0.3, + "dsa": { + "behalf": "Advertiser", + "paid": "Advertiser", + "transparency": [{ + "domain": "dsp1domain.com", + "dsaparams": [1, 2] + }], + "adrender": 1 + } + } + }, + "bidType": "video" + }], + "seat": "appnexus" + }] + } + }, + "audienceNetwork": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "bidder": { + "placementId": "some-placement" + } + } + }], + "regs": { + "ext": { + "dsa": { + "dsarequired": 2, + "pubrender": 0, + "datatopub": 2, + "transparency": [{ + "domain": "platform1domain.com", + "dsaparams": [1] + }, + { + "domain": "SSP2domain.com", + "dsaparams": [1, 2] + } + ] + } + } + } + } + }, + "mockResponse": { + "pbsSeatBids": [{ + "pbsBids": [{ + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "origbidcpm": 0.3 + } + }, + "bidType": "video" + }], + "seat": "audienceNetwork" + }] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [{ + "seat": "appnexus", + "bid": [{ + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "origbidcpm": 0.3, + "prebid": { + "meta": { + }, + "type": "video" + }, + "dsa": { + "behalf": "Advertiser", + "paid": "Advertiser", + "transparency": [{ + "domain": "dsp1domain.com", + "dsaparams": [1, 2] + }], + "adrender": 1 + } + } + }] + },{ + "seat": "audienceNetwork", + "bid": [] + }] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/eidpermissions-allowed-alias.json b/exchange/exchangetest/eidpermissions-allowed-alias.json index de3c0e1dca6..886927723c1 100644 --- a/exchange/exchangetest/eidpermissions-allowed-alias.json +++ b/exchange/exchangetest/eidpermissions-allowed-alias.json @@ -135,6 +135,8 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + }, "type": "video" } } diff --git a/exchange/exchangetest/eidpermissions-allowed-case-insensitive.json b/exchange/exchangetest/eidpermissions-allowed-case-insensitive.json index 86797501495..70d3d0be73c 100644 --- a/exchange/exchangetest/eidpermissions-allowed-case-insensitive.json +++ b/exchange/exchangetest/eidpermissions-allowed-case-insensitive.json @@ -132,6 +132,8 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + }, "type": "video" } } diff --git a/exchange/exchangetest/eidpermissions-allowed.json b/exchange/exchangetest/eidpermissions-allowed.json index 9daee27a9b6..045e1246ca7 100644 --- a/exchange/exchangetest/eidpermissions-allowed.json +++ b/exchange/exchangetest/eidpermissions-allowed.json @@ -132,6 +132,8 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + }, "type": "video" } } diff --git a/exchange/exchangetest/eidpermissions-denied.json b/exchange/exchangetest/eidpermissions-denied.json index 04eb51bb600..d7b5eeea43d 100644 --- a/exchange/exchangetest/eidpermissions-denied.json +++ b/exchange/exchangetest/eidpermissions-denied.json @@ -6,18 +6,16 @@ "page": "test.somepage.com" }, "user": { - "ext": { - "eids": [ - { - "source": "source1", - "uids": [ - { - "id": "id1" - } - ] - } - ] - } + "eids": [ + { + "source": "source1", + "uids": [ + { + "id": "id1" + } + ] + } + ] }, "ext": { "prebid": { @@ -119,6 +117,8 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + }, "type": "video" } } diff --git a/exchange/exchangetest/events-bid-account-off-request-off.json b/exchange/exchangetest/events-bid-account-off-request-off.json index b15b33a8653..48906439547 100644 --- a/exchange/exchangetest/events-bid-account-off-request-off.json +++ b/exchange/exchangetest/events-bid-account-off-request-off.json @@ -79,6 +79,8 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + }, "type": "banner" } } @@ -93,6 +95,8 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + }, "type": "banner" } } diff --git a/exchange/exchangetest/events-bid-account-off-request-on.json b/exchange/exchangetest/events-bid-account-off-request-on.json index 8a6b8aa7e14..9166bb43cb8 100644 --- a/exchange/exchangetest/events-bid-account-off-request-on.json +++ b/exchange/exchangetest/events-bid-account-off-request-on.json @@ -82,6 +82,8 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + }, "type": "banner", "events": { "imp": "http://localhost/event?t=imp&b=winning-bid&a=testaccount&bidder=appnexus&int=testIntegrationType&ts=1234567890", @@ -100,6 +102,8 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + }, "type": "banner", "events": { "imp": "http://localhost/event?t=imp&b=losing-bid&a=testaccount&bidder=appnexus&int=testIntegrationType&ts=1234567890", diff --git a/exchange/exchangetest/events-bid-account-on-request-off.json b/exchange/exchangetest/events-bid-account-on-request-off.json index 5a2fa6ae4d2..536fdb692e0 100644 --- a/exchange/exchangetest/events-bid-account-on-request-off.json +++ b/exchange/exchangetest/events-bid-account-on-request-off.json @@ -81,6 +81,8 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + }, "type": "banner", "events": { "imp": "http://localhost/event?t=imp&b=winning-bid&a=testaccount&bidder=appnexus&int=testIntegrationType&ts=1234567890", @@ -99,6 +101,8 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + }, "type": "banner", "events": { "imp": "http://localhost/event?t=imp&b=losing-bid&a=testaccount&bidder=appnexus&int=testIntegrationType&ts=1234567890", diff --git a/exchange/exchangetest/events-vast-account-off-request-off.json b/exchange/exchangetest/events-vast-account-off-request-off.json index a94546d2aa0..9f2e4fe0ab2 100644 --- a/exchange/exchangetest/events-vast-account-off-request-off.json +++ b/exchange/exchangetest/events-vast-account-off-request-off.json @@ -125,6 +125,8 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + }, "type": "video" } } @@ -145,6 +147,8 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -168,6 +172,8 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + }, "type": "video" } } diff --git a/exchange/exchangetest/events-vast-account-off-request-on.json b/exchange/exchangetest/events-vast-account-off-request-on.json index 275b8aef2ce..560f4f0e2c7 100644 --- a/exchange/exchangetest/events-vast-account-off-request-on.json +++ b/exchange/exchangetest/events-vast-account-off-request-on.json @@ -131,7 +131,9 @@ "ext": { "origbidcpm": 0.51, "prebid": { - "bidid": "mock_uuid", + "meta": { + }, + "bidid": "bid-audienceNetwork-1", "type": "video" } } @@ -143,7 +145,7 @@ "bid": [ { "id": "winning-bid", - "adm": "prebid.org wrapper", + "adm": "prebid.org wrapper", "nurl": "http://domain.com/winning-bid", "impid": "my-imp-id", "price": 0.71, @@ -153,7 +155,9 @@ "ext": { "origbidcpm": 0.71, "prebid": { - "bidid": "mock_uuid", + "meta": { + }, + "bidid": "bid-appnexus-1", "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -168,7 +172,7 @@ }, { "id": "losing-bid", - "adm": "prebid.org wrapper", + "adm": "prebid.org wrapper", "nurl": "http://domain.com/losing-bid", "impid": "my-imp-id", "price": 0.21, @@ -178,7 +182,9 @@ "ext": { "origbidcpm": 0.21, "prebid": { - "bidid": "mock_uuid", + "meta": { + }, + "bidid": "bid-appnexus-2", "type": "video" } } diff --git a/exchange/exchangetest/events-vast-account-on-request-off.json b/exchange/exchangetest/events-vast-account-on-request-off.json index e271ecf5c8f..882f1e8c623 100644 --- a/exchange/exchangetest/events-vast-account-on-request-off.json +++ b/exchange/exchangetest/events-vast-account-on-request-off.json @@ -126,6 +126,8 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + }, "type": "video" } } @@ -147,6 +149,8 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -171,6 +175,8 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + }, "type": "video" } } diff --git a/exchange/exchangetest/extra-bids-with-aliases-adaptercode.json b/exchange/exchangetest/extra-bids-with-aliases-adaptercode.json index b46f54c6408..754253978bb 100644 --- a/exchange/exchangetest/extra-bids-with-aliases-adaptercode.json +++ b/exchange/exchangetest/extra-bids-with-aliases-adaptercode.json @@ -81,7 +81,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pmbidder" } }, { @@ -95,7 +94,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pmbidder" } }, { @@ -109,7 +107,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pmbidder" } } ], @@ -128,7 +125,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pmbidder" } } ], @@ -156,7 +152,6 @@ "origbidcpm": 0.51, "prebid": { "meta": { - "adaptercode": "pmbidder" }, "type": "video", "targeting": { @@ -185,7 +180,6 @@ "origbidcpm": 0.71, "prebid": { "meta": { - "adaptercode": "pmbidder" }, "type": "video", "targeting": { @@ -214,7 +208,6 @@ "origbidcpm": 0.21, "prebid": { "meta": { - "adaptercode": "pmbidder" }, "type": "video" } @@ -231,7 +224,6 @@ "origbidcpm": 0.61, "prebid": { "meta": { - "adaptercode": "pmbidder" }, "type": "video", "targeting": { diff --git a/exchange/exchangetest/extra-bids.json b/exchange/exchangetest/extra-bids.json index 23e122593b5..e1682618c60 100644 --- a/exchange/exchangetest/extra-bids.json +++ b/exchange/exchangetest/extra-bids.json @@ -146,7 +146,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } }, { @@ -160,7 +159,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } }, { @@ -174,7 +172,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } } ], @@ -193,7 +190,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } } ], @@ -262,7 +258,6 @@ }, "bidType": "banner", "bidMeta": { - "adaptercode": "appnexus" } } ], @@ -281,7 +276,6 @@ }, "bidType": "banner", "bidMeta": { - "adaptercode": "appnexus" } } ], @@ -309,7 +303,6 @@ "origbidcpm": 0.51, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video", "targeting": { @@ -333,7 +326,6 @@ "origbidcpm": 0.3, "prebid": { "meta": { - "adaptercode": "appnexus" }, "type": "banner" } @@ -355,7 +347,6 @@ "origbidcpm": 0.71, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video", "targeting": { @@ -384,7 +375,6 @@ "origbidcpm": 0.21, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video" } @@ -401,7 +391,6 @@ "origbidcpm": 0.61, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video", "targeting": { @@ -435,7 +424,6 @@ "origbidcpm": 0.3, "prebid": { "meta": { - "adaptercode": "appnexus" }, "type": "banner", "targeting": { diff --git a/exchange/exchangetest/firstpartydata-amp-imp-ext-one-prebid-bidder.json b/exchange/exchangetest/firstpartydata-amp-imp-ext-one-prebid-bidder.json index 627c95f9d54..f1119b22215 100644 --- a/exchange/exchangetest/firstpartydata-amp-imp-ext-one-prebid-bidder.json +++ b/exchange/exchangetest/firstpartydata-amp-imp-ext-one-prebid-bidder.json @@ -121,6 +121,8 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + }, "type": "banner" } } diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json index f15fea59d87..edeb0423582 100644 --- a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json +++ b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json @@ -188,6 +188,8 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + }, "type": "banner" } } @@ -207,6 +209,8 @@ "ext": { "origbidcpm": 0.4, "prebid": { + "meta": { + }, "type": "banner" } } diff --git a/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json index 9eb6a77eed5..ef0ad225286 100644 --- a/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json +++ b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json @@ -121,6 +121,8 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + }, "type": "banner" } } diff --git a/exchange/exchangetest/firstpartydata-multibidder-user-eids.json b/exchange/exchangetest/firstpartydata-multibidder-user-eids.json new file mode 100644 index 00000000000..f16907dcdb2 --- /dev/null +++ b/exchange/exchangetest/firstpartydata-multibidder-user-eids.json @@ -0,0 +1,239 @@ +{ + "requestType": "openrtb2-web", + "ortbversion": { + "appnexus":"2.6", + "rubicon":"2.6" + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "id": "reqUserID", + "keywords": "userKeyword!", + "eids": [ + { + "source": "reqeid1", + "uids": [ + { + "id": "reqeiduid1" + } + ] + }, + { + "source": "reqieid2", + "uids": [ + { + "id": "reqeiduid2" + } + ] + } + ] + }, + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + } + } + } + ], + "ext": { + "prebid": { + "data": { + "eidpermissions": [ + { + "source": "reqeid1", + "bidders": [ + "rubicon" + ] + } + ], + "bidders": [ + "appnexus", + "rubicon" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "domain": "fpd_appnexus_site_domain", + "page": "fpd_appnexus_site_page" + } + } + } + }, + { + "bidders": [ + "rubicon" + ], + "config": { + "ortb2": { + "user": { + "id": "fpdSiteId!4", + "yob": 2000, + "keywords": "fpd keywords", + "eids": [ + { + "source": "fpdeid1", + "uids": [ + { + "id": "fpdeiduid1" + } + ] + }, + { + "source": "fpdeid2", + "uids": [ + { + "id": "fpdeiduid1" + } + ] + } + ] + } + } + } + } + ] + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "site": { + "domain": "fpd_appnexus_site_domain", + "page": "fpd_appnexus_site_page" + }, + "user": { + "id": "reqUserID", + "keywords": "userKeyword!", + "eids": [ + { + "source": "reqieid2", + "uids": [ + { + "id": "reqeiduid2" + } + ] + } + ] + } + } + } + }, + "rubicon": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + } + ], + "site": { + "page": "test.somepage.com" + }, + "user": { + "id": "fpdSiteId!4", + "yob": 2000, + "keywords": "fpd keywords", + "eids": [ + { + "source": "fpdeid1", + "uids": [ + { + "id": "fpdeiduid1" + } + ] + }, + { + "source": "fpdeid2", + "uids": [ + { + "id": "fpdeiduid1" + } + ] + } + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-user-eids-req-user-nil.json b/exchange/exchangetest/firstpartydata-user-eids-req-user-nil.json new file mode 100644 index 00000000000..2c3b47dc76e --- /dev/null +++ b/exchange/exchangetest/firstpartydata-user-eids-req-user-nil.json @@ -0,0 +1,203 @@ +{ + "requestType": "openrtb2-web", + "ortbversion": { + "appnexus":"2.6", + "rubicon":"2.6" + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + } + } + } + ], + "ext": { + "prebid": { + "data": { + "eidpermissions": [ + { + "source": "reqeid1", + "bidders": [ + "rubicon" + ] + } + ], + "bidders": [ + "appnexus", + "rubicon" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "domain": "fpd_appnexus_site_domain", + "page": "fpd_appnexus_site_page" + } + } + } + }, + { + "bidders": [ + "rubicon" + ], + "config": { + "ortb2": { + "user": { + "id": "fpdSiteId!4", + "yob": 2000, + "keywords": "fpd keywords", + "eids": [ + { + "source": "fpdeid1", + "uids": [ + { + "id": "fpdeiduid1" + } + ] + }, + { + "source": "fpdeid2", + "uids": [ + { + "id": "fpdeiduid1" + } + ] + } + ] + } + } + } + } + ] + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "site": { + "domain": "fpd_appnexus_site_domain", + "page": "fpd_appnexus_site_page" + } + } + } + }, + "rubicon": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + } + ], + "site": { + "page": "test.somepage.com" + }, + "user": { + "id": "fpdSiteId!4", + "yob": 2000, + "keywords": "fpd keywords", + "eids": [ + { + "source": "fpdeid1", + "uids": [ + { + "id": "fpdeiduid1" + } + ] + }, + { + "source": "fpdeid2", + "uids": [ + { + "id": "fpdeiduid1" + } + ] + } + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-user-nileids-req-user-nil.json b/exchange/exchangetest/firstpartydata-user-nileids-req-user-nil.json new file mode 100644 index 00000000000..3ea8382176c --- /dev/null +++ b/exchange/exchangetest/firstpartydata-user-nileids-req-user-nil.json @@ -0,0 +1,163 @@ +{ + "requestType": "openrtb2-web", + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + } + } + } + ], + "ext": { + "prebid": { + "data": { + "eidpermissions": [ + { + "source": "reqeid1", + "bidders": [ + "rubicon" + ] + } + ], + "bidders": [ + "appnexus", + "rubicon" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "domain": "fpd_appnexus_site_domain", + "page": "fpd_appnexus_site_page" + } + } + } + }, + { + "bidders": [ + "rubicon" + ], + "config": { + "ortb2": { + "user": { + "id": "fpdSiteId!4", + "yob": 2000, + "keywords": "fpd keywords" + } + } + } + } + ] + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "site": { + "domain": "fpd_appnexus_site_domain", + "page": "fpd_appnexus_site_page" + } + } + } + }, + "rubicon": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + } + ], + "site": { + "page": "test.somepage.com" + }, + "user": { + "id": "fpdSiteId!4", + "yob": 2000, + "keywords": "fpd keywords" + } + } + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/fledge-with-bids.json b/exchange/exchangetest/fledge-with-bids.json index 0f998e69ee0..030467f6878 100644 --- a/exchange/exchangetest/fledge-with-bids.json +++ b/exchange/exchangetest/fledge-with-bids.json @@ -114,6 +114,8 @@ "origbidcpm": 0.3, "someField": "someValue", "prebid": { + "meta": { + }, "type": "video" } } diff --git a/exchange/exchangetest/floors_deal_enforcement.json b/exchange/exchangetest/floors_deal_enforcement.json new file mode 100644 index 00000000000..924fd2f7719 --- /dev/null +++ b/exchange/exchangetest/floors_deal_enforcement.json @@ -0,0 +1,147 @@ +{ + "floors_enabled": true, + "account_floors_enabled": true, + "account_enforce_deal_floors": true, + "incomingRequest": { + "ortbRequest": { + "id": "request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "bidfloor": 20, + "bidfloorcur": "USD", + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "pubmatic": { + "publisherId": "1234" + } + } + } + } + } + ], + "ext": { + "prebid": { + "floors": { + "enabled": true, + "enforcement": { + "floordeals": true, + "enforcerate": 100 + } + } + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apnx-bid-id", + "dealid": "apnx-deal-id", + "impid": "imp-id", + "price": 5, + "w": 200, + "h": 250, + "crid": "creative-1" + } + } + ], + "seat": "appnexus", + "currency": "USD" + } + ] + } + }, + "pubmatic": { + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "pubm-bid-id", + "impid": "imp-id", + "price": 10, + "w": 200, + "h": 250, + "crid": "creative-1" + } + } + ], + "seat": "pubmatic", + "currency": "USD" + } + ] + } + } + }, + "response": { + "bids": {}, + "ext": { + "prebid": { + "seatnonbid": [ + { + "nonbid": [ + { + "impid": "imp-id", + "statuscode": 304, + "ext": { + "prebid": { + "bid": { + "price": 5, + "w": 200, + "h": 250, + "crid": "creative-1", + "origbidcpm": 5, + "dealid": "apnx-deal-id" + } + } + } + } + ], + "seat": "appnexus", + "ext": null + }, + { + "nonbid": [ + { + "impid": "imp-id", + "statuscode": 301, + "ext": { + "prebid": { + "bid": { + "price": 10, + "w": 200, + "h": 250, + "crid": "creative-1", + "origbidcpm": 10 + } + } + } + } + ], + "seat": "pubmatic", + "ext": null + } + ] + } + } + } + } diff --git a/exchange/exchangetest/floors_enforcement.json b/exchange/exchangetest/floors_enforcement.json index 90d25301e65..d280c412713 100644 --- a/exchange/exchangetest/floors_enforcement.json +++ b/exchange/exchangetest/floors_enforcement.json @@ -136,6 +136,8 @@ "ext": { "origbidcpm": 12, "prebid": { + "meta": { + }, "floors": { "floorCurrency": "USD", "floorRule": "*|*", @@ -148,6 +150,35 @@ ] } ] + }, + "ext": { + "prebid": { + "seatnonbid": [ + { + "nonbid": [ + { + "impid": "my-imp-id", + "statuscode": 301, + "ext": { + "prebid": { + "bid": { + "price": 7, + "w": 200, + "h": 250, + "origbidcpm": 7, + "cat": [ + "IAB1-1" + ] + } + } + } + } + ], + "seat": "audienceNetwork", + "ext": null + } + ] } + } } - } \ No newline at end of file + } diff --git a/exchange/exchangetest/generate-bid-id-error.json b/exchange/exchangetest/generate-bid-id-error.json new file mode 100644 index 00000000000..3536f38b3d1 --- /dev/null +++ b/exchange/exchangetest/generate-bid-id-error.json @@ -0,0 +1,203 @@ +{ + "bidIDGenerator": { + "generateBidID": true, + "returnError": true + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } + } + } + ], + "test": 1, + "ext": { + "prebid": { + "targeting": { + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "", + "withcategory": true + }, + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "min": 0, + "max": 20, + "increment": 0.1 + } + ] + }, + "includewinners": true, + "includebidderkeys": true, + "appendbiddernames": true + } + } + } + }, + "usersyncs": { + "appnexus": "123" + } + }, + "outgoingRequests": { + "appnexus": { + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ] + }, + "bidType": "video", + "bidVideo": { + "duration": 30, + "PrimaryCategory": "" + } + } + ], + "seat": "appnexus" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ], + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": { + }, + "type": "video", + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "0.20", + "hb_pb_appnexus": "0.20", + "hb_pb_cat_dur": "0.20_VideoGames_30s_appnexus", + "hb_pb_cat_dur_appnex": "0.20_VideoGames_30s_appnexus", + "hb_size": "200x250", + "hb_size_appnexus": "200x250" + }, + "video": { + "duration": 30, + "primary_category": "" + } + } + } + } + ] + } + ] + }, + "ext": { + "debug": { + "resolvedrequest": { + "id": "some-request-id", + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } + } + } + ], + "site": { + "page": "test.somepage.com" + }, + "test": 1, + "ext": { + "prebid": { + "targeting": { + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "", + "withcategory": true + }, + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "min": 0, + "max": 20, + "increment": 0.1 + } + ] + }, + "includewinners": true, + "includebidderkeys": true, + "appendbiddernames": true + } + } + } + } + }, + "errors": { + "prebid": [ + { + "code": 999, + "message": "Error generating bid.ext.prebid.bidid" + } + ] + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/generate-bid-id-many.json b/exchange/exchangetest/generate-bid-id-many.json new file mode 100644 index 00000000000..ecdefbaa71b --- /dev/null +++ b/exchange/exchangetest/generate-bid-id-many.json @@ -0,0 +1,170 @@ +{ + "bidIDGenerator": { + "generateBidID": true, + "returnError": false + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } + } + } + ] + } + }, + "outgoingRequests": { + "appnexus": { + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid-1", + "impid": "imp-id-1", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ] + }, + "bidType": "video", + "bidVideo": { + "duration": 30 + } + }, + { + "ortbBid": { + "id": "apn-bid-2", + "impid": "imp-id-1", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ] + }, + "bidType": "video", + "bidVideo": { + "duration": 30, + "PrimaryCategory": "" + } + } + ], + "seat": "appnexus" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid-1", + "impid": "imp-id-1", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ], + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": { + }, + "bidid": "bid-appnexus-1", + "type": "video", + "video": { + "duration": 30, + "primary_category": "" + } + } + } + }, + { + "id": "apn-bid-2", + "impid": "imp-id-1", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ], + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": { + }, + "bidid": "bid-appnexus-2", + "type": "video", + "video": { + "duration": 30, + "primary_category": "" + } + } + } + } + ] + } + ] + }, + "ext": { + "debug": { + "resolvedrequest": { + "id": "some-request-id", + "imp": [ + { + "id": "imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } + } + } + ], + "site": { + "page": "test.somepage.com" + } + } + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/generate-bid-id-one.json b/exchange/exchangetest/generate-bid-id-one.json new file mode 100644 index 00000000000..ccd6abd05f2 --- /dev/null +++ b/exchange/exchangetest/generate-bid-id-one.json @@ -0,0 +1,129 @@ +{ + "bidIDGenerator": { + "generateBidID": true, + "returnError": false + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } + } + } + ] + } + }, + "outgoingRequests": { + "appnexus": { + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "imp-id-1", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ] + }, + "bidType": "video", + "bidVideo": { + "duration": 30, + "primary_category": "" + } + } + ], + "seat": "appnexus" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "imp-id-1", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ], + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": { + }, + "bidid": "bid-appnexus-1", + "type": "video", + "video": { + "duration": 30, + "primary_category": "" + } + } + } + } + ] + } + ] + }, + "ext": { + "debug": { + "resolvedrequest": { + "id": "some-request-id", + "imp": [ + { + "id": "imp-id-1", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + } + } + } + ], + "site": { + "page": "test.somepage.com" + } + } + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/imp-fpd-invalid.json b/exchange/exchangetest/imp-fpd-invalid.json new file mode 100644 index 00000000000..6d0bd9b9f76 --- /dev/null +++ b/exchange/exchangetest/imp-fpd-invalid.json @@ -0,0 +1,55 @@ +{ + "requestType": "openrtb2-web", + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "audienceNetwork": { + "placementId": "some-placement" + } + }, + "imp": { + "appnexus": { + "id": "imp-id-1-appnexus" + }, + "audienceNetwork": { + "banner": { + "format": [ + { + "w": -1, + "h": -1 + } + ] + } + } + } + } + } + } + ] + } + }, + "outgoingRequests": {}, + "response": { + "error": "merging bidder imp first party data for imp imp-id-1 results in an invalid imp: [request.imp[0].banner.format[0].w must be a positive number]" + } +} \ No newline at end of file diff --git a/exchange/exchangetest/imp-fpd-multiple-bidders.json b/exchange/exchangetest/imp-fpd-multiple-bidders.json new file mode 100644 index 00000000000..411435d9b2a --- /dev/null +++ b/exchange/exchangetest/imp-fpd-multiple-bidders.json @@ -0,0 +1,202 @@ +{ + "requestType": "openrtb2-web", + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "audienceNetwork": { + "placementId": "some-placement" + } + }, + "imp": { + "appnexus": { + "id": "imp-id-1-appnexus" + }, + "audienceNetwork": { + "banner": { + "format": [ + { + "w": 3000, + "h": 6000 + } + ] + } + }, + "rubicon": { + "banner": { + "w": 150, + "h": 300 + } + } + } + } + } + } + ] + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id-1-appnexus", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ] + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "imp-id-1-appnexus", + "price": 0.3, + "w": 300, + "h": 600, + "crid": "creative-1" + }, + "bidType": "banner" + } + ], + "seat": "appnexus" + } + ] + } + }, + "audienceNetwork": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id-1", + "banner": { + "format": [ + { + "w": 3000, + "h": 6000 + } + ] + }, + "ext": { + "bidder": { + "placementId": "some-placement" + } + } + } + ] + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "imp-id-1", + "price": 0.3, + "w": 3000, + "h": 6000, + "crid": "creative-1" + }, + "bidType": "banner" + } + ], + "seat": "audiencenetwork" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "imp-id-1-appnexus", + "price": 0.3, + "w": 300, + "h": 600, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": {}, + "type": "banner" + } + } + } + ] + }, + { + "seat": "audiencenetwork", + "bid": [ + { + "id": "apn-bid", + "impid": "imp-id-1", + "price": 0.3, + "w": 3000, + "h": 6000, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": {}, + "type": "banner" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/imp-fpd-multiple-imp.json b/exchange/exchangetest/imp-fpd-multiple-imp.json new file mode 100644 index 00000000000..11261cd1349 --- /dev/null +++ b/exchange/exchangetest/imp-fpd-multiple-imp.json @@ -0,0 +1,290 @@ +{ + "requestType": "openrtb2-web", + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id-1", + "banner": { + "w": 100, + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + }, + "imp": { + "appnexus": { + "banner": { + "h": 200, + "format": [ + { + "w": 3000, + "h": 6000 + } + ], + "btype": [1,2] + } + } + } + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": [ + "video/mp3" + ], + "w": 200 + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + }, + "imp": { + "appnexus": { + "video": { + "mimes": [ + "video/mp4" + ], + "h": 400 + } + } + } + } + } + }, + { + "id": "imp-id-3", + "audio": { + "mimes": ["audio/mp4"], + "minduration": 1, + "maxduration": 10, + "durfloors": [{ + "mindur": 1, + "maxdur": 10, + "bidfloor": 10.0 + }] + }, + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}}]}", + "ver": "1.1" + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + }, + "imp": { + "appnexus": { + "audio": { + "maxduration": 20, + "durfloors": [{ + "mindur": 2, + "maxdur": 30, + "bidfloor": 20.0 + }], + "poddur": 40 + }, + "native": { + "ver": "2.2", + "ctype": 1 + } + } + } + } + } + } + ] + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id-1", + "banner": { + "w": 100, + "h": 200, + "format": [ + { + "w": 3000, + "h": 6000 + } + ], + "btype": [1,2] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 200, + "h": 400 + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + }, + { + "id": "imp-id-3", + "audio": { + "mimes": ["audio/mp4"], + "minduration": 1, + "maxduration": 20, + "durfloors": [{ + "mindur": 2, + "maxdur": 30, + "bidfloor": 20.0 + }], + "poddur": 40 + }, + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}}]}", + "ver": "2.2", + "ctype": 1 + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ] + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid-1", + "impid": "imp-id-1", + "price": 0.1, + "w": 3000, + "h": 6000, + "crid": "creative-1" + }, + "bidType": "banner" + }, + { + "ortbBid": { + "id": "apn-bid-2", + "impid": "imp-id-2", + "price": 0.2, + "w": 200, + "h": 400, + "crid": "creative-2" + }, + "bidType": "video" + }, + { + "ortbBid": { + "id": "apn-bid-3", + "impid": "imp-id-3", + "price": 0.3, + "w": 300, + "h": 600, + "crid": "creative-3" + }, + "bidType": "native" + } + ], + "seat": "appnexus" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid-1", + "impid": "imp-id-1", + "price": 0.1, + "w": 3000, + "h": 6000, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.1, + "prebid": { + "meta": {}, + "type": "banner" + } + } + }, + { + "id": "apn-bid-2", + "impid": "imp-id-2", + "price": 0.2, + "w": 200, + "h": 400, + "crid": "creative-2", + "ext": { + "origbidcpm": 0.2, + "prebid": { + "meta": {}, + "type": "video" + } + } + }, + { + "id": "apn-bid-3", + "impid": "imp-id-3", + "price": 0.3, + "w": 300, + "h": 600, + "crid": "creative-3", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": {}, + "type": "native" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/imp-fpd-skipped-validation.json b/exchange/exchangetest/imp-fpd-skipped-validation.json new file mode 100644 index 00000000000..d6285a02fbe --- /dev/null +++ b/exchange/exchangetest/imp-fpd-skipped-validation.json @@ -0,0 +1,137 @@ +{ + "requestType": "openrtb2-web", + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id-1", + "native": {}, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": [] + } + }, + "imp": { + "appnexus": { + "id": "imp-id-1-appnexus" + } + } + } + } + }, + { + "id": "imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": "123" + } + } + } + } + } + ] + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "imp-id-1-appnexus", + "native": {}, + "ext": { + "bidder": { + "placementId": [] + } + } + }, + { + "id": "imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "123" + } + } + } + ] + } + }, + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "imp-id-2", + "price": 0.3, + "w": 300, + "h": 600, + "crid": "creative-1" + }, + "bidType": "banner" + } + ], + "seat": "appnexus" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "appnexus", + "bid": [ + { + "id": "apn-bid", + "impid": "imp-id-2", + "price": 0.3, + "w": 300, + "h": 600, + "crid": "creative-1", + "ext": { + "origbidcpm": 0.3, + "prebid": { + "meta": {}, + "type": "banner" + } + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/include-brand-category.json b/exchange/exchangetest/include-brand-category.json index 9d537f74902..7334cf1a6c1 100644 --- a/exchange/exchangetest/include-brand-category.json +++ b/exchange/exchangetest/include-brand-category.json @@ -134,6 +134,8 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -144,10 +146,14 @@ "hb_cache_path_appnex": "/pbcache/endpoint", "hb_pb": "0.20", "hb_pb_appnexus": "0.20", - "hb_pb_cat_dur": "0.20_VideoGames_0s", - "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s", + "hb_pb_cat_dur": "0.20_VideoGames_30s", + "hb_pb_cat_dur_appnex": "0.20_VideoGames_30s", "hb_size": "200x250", "hb_size_appnexus": "200x250" + }, + "video": { + "duration": 30, + "primary_category": "" } } } @@ -160,6 +166,8 @@ "ext": { "origbidcpm": 0.6, "prebid": { + "meta": { + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/mediatypepricegranularity-banner-video-native.json b/exchange/exchangetest/mediatypepricegranularity-banner-video-native.json index 7d119b23044..10dacd226a6 100644 --- a/exchange/exchangetest/mediatypepricegranularity-banner-video-native.json +++ b/exchange/exchangetest/mediatypepricegranularity-banner-video-native.json @@ -196,6 +196,8 @@ "ext": { "origbidcpm": 15, "prebid": { + "meta": { + }, "type": "banner", "targeting": { "hb_bidder": "appnexus", @@ -223,6 +225,8 @@ "ext": { "origbidcpm": 18, "prebid": { + "meta": { + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", @@ -255,6 +259,8 @@ "ext": { "origbidcpm": 29, "prebid": { + "meta": { + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/mediatypepricegranularity-native.json b/exchange/exchangetest/mediatypepricegranularity-native.json index 57a318e1fcf..a7a04b6788a 100644 --- a/exchange/exchangetest/mediatypepricegranularity-native.json +++ b/exchange/exchangetest/mediatypepricegranularity-native.json @@ -164,6 +164,8 @@ "ext": { "origbidcpm": 24, "prebid": { + "meta": { + }, "type": "native", "targeting": { "hb_bidder": "appnexus", @@ -191,6 +193,8 @@ "ext": { "origbidcpm": 29, "prebid": { + "meta": { + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/multi-bid-default-bid-limit.json b/exchange/exchangetest/multi-bid-default-bid-limit.json index 45e3c673988..d94fc4a245d 100644 --- a/exchange/exchangetest/multi-bid-default-bid-limit.json +++ b/exchange/exchangetest/multi-bid-default-bid-limit.json @@ -267,7 +267,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "appnexus" } }, { @@ -295,7 +294,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "appnexus" } }, { @@ -323,7 +321,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "appnexus" } } ], @@ -471,7 +468,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "rubicon" } }, { @@ -498,7 +494,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "rubicon" } }, { @@ -525,7 +520,6 @@ }, "bidType": "banner", "bidMeta": { - "adaptercode": "rubicon" } } ], @@ -552,7 +546,6 @@ "ext": { "prebid": { "meta": { - "adaptercode": "rubicon" }, "type": "video", "targeting": { @@ -593,7 +586,6 @@ "ext": { "prebid": { "meta": { - "adaptercode": "rubicon" }, "type": "video", "targeting": { @@ -638,7 +630,6 @@ "ext": { "prebid": { "meta": { - "adaptercode": "appnexus" }, "type": "video", "targeting": { @@ -674,7 +665,6 @@ "ext": { "prebid": { "meta": { - "adaptercode": "appnexus" }, "type": "video" }, diff --git a/exchange/exchangetest/multi-bids-different-ortb-versions.json b/exchange/exchangetest/multi-bids-different-ortb-versions.json new file mode 100644 index 00000000000..37e7fc582c7 --- /dev/null +++ b/exchange/exchangetest/multi-bids-different-ortb-versions.json @@ -0,0 +1,276 @@ +{ + "requestType": "openrtb2-web", + "ortbversion": { + "appnexus":"2.6", + "rubicon":"2.5" + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "id": "my-user-id", + "buyeruid": "my-buyer-uid", + "geo": { + "lat": 123.456, + "lon": 678.90, + "zip": "90210" + }, + "consent": "AAAA", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ], + "ext": {} + }, + "imp": [ + { + "rwdd": 1, + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + } + } + } + ], + "source": { + "fd": 1, + "tid": "abc123", + "pchain": "tag_placement", + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "asi", + "sid": "sid", + "rid": "rid", + "ext": {} + } + ], + "ver": "ver", + "ext": {} + }, + "ext": {} + }, + "regs": { + "coppa": 1, + "gdpr": 1, + "us_privacy": "1YYY", + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1NYN", + "gppsid": [ + 6 + ] + } + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "geo": {}, + "consent": "AAAA", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ], + "ext": {} + }, + "imp": [ + { + "rwdd": 1, + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": 1 + } + } + } + ], + "source": { + "fd": 1, + "tid": "abc123", + "pchain": "tag_placement", + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "asi", + "sid": "sid", + "rid": "rid", + "ext": {} + } + ], + "ver": "ver", + "ext": {} + }, + "ext": {} + }, + "regs": { + "coppa": 1, + "gdpr": 1, + "us_privacy": "1YYY", + "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1NYN" + } + } + } + }, + "rubicon": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "user": { + "geo": {}, + "ext": { + "consent": "AAAA", + "eids": [ + { + "source": "source", + "uids": [ + { + "id": "1", + "atype": 1, + "ext": {} + }, + { + "id": "1", + "atype": 1, + "ext": {} + } + ], + "ext": {} + } + ] + } + }, + "imp": [ + { + "id": "some-imp-id", + "banner": { + "format": [ + { + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + }, + "prebid": { + "is_rewarded_inventory":1 + } + } + } + ], + "source": { + "fd": 1, + "tid": "abc123", + "pchain": "tag_placement", + "ext": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "asi", + "sid": "sid", + "rid": "rid", + "ext": {} + } + ], + "ver": "ver", + "ext": {} + } + } + }, + "regs": { + "coppa": 1, + "gpp":"DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1NYN", + "ext": { + "gdpr": 1, + "us_privacy": "1YYY" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/multi-bids-error.json b/exchange/exchangetest/multi-bids-error.json index fc96b5ea1ef..35ad1d93a37 100644 --- a/exchange/exchangetest/multi-bids-error.json +++ b/exchange/exchangetest/multi-bids-error.json @@ -180,7 +180,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } }, { @@ -194,7 +193,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } }, { @@ -208,7 +206,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } }, { @@ -222,7 +219,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } } ], @@ -283,7 +279,6 @@ }, "bidType": "banner", "bidMeta": { - "adaptercode": "appnexus" } }, { @@ -297,7 +292,6 @@ }, "bidType": "banner", "bidMeta": { - "adaptercode": "appnexus" } } ], @@ -325,7 +319,6 @@ "origbidcpm": 0.71, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video", "targeting": { @@ -355,7 +348,6 @@ "origbidcpm": 0.21, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video", "targeting": { @@ -380,7 +372,6 @@ "origbidcpm": 0.61, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video", "targeting": { @@ -410,7 +401,6 @@ "origbidcpm": 0.51, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video", "targeting": { @@ -440,7 +430,6 @@ "origbidcpm": 0.3, "prebid": { "meta": { - "adaptercode": "appnexus" }, "type": "banner", "targeting": { @@ -464,7 +453,6 @@ "origbidcpm": 0.3, "prebid": { "meta": { - "adaptercode": "appnexus" }, "type": "banner" } diff --git a/exchange/exchangetest/multi-bids-mixed-case.json b/exchange/exchangetest/multi-bids-mixed-case.json index 44630b3068c..3ecdb123921 100644 --- a/exchange/exchangetest/multi-bids-mixed-case.json +++ b/exchange/exchangetest/multi-bids-mixed-case.json @@ -146,7 +146,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } }, { @@ -160,7 +159,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } }, { @@ -174,7 +172,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } }, { @@ -188,7 +185,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } } ], @@ -261,7 +257,6 @@ }, "bidType": "banner", "bidMeta": { - "adaptercode": "appnexus" } }, { @@ -275,7 +270,6 @@ }, "bidType": "banner", "bidMeta": { - "adaptercode": "appnexus" } } ], @@ -303,7 +297,6 @@ "origbidcpm": 0.71, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video", "targeting": { @@ -333,7 +326,6 @@ "origbidcpm": 0.21, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video" } @@ -350,7 +342,6 @@ "origbidcpm": 0.61, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video", "targeting": { @@ -380,7 +371,6 @@ "origbidcpm": 0.51, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video", "targeting": { @@ -410,7 +400,6 @@ "origbidcpm": 0.3, "prebid": { "meta": { - "adaptercode": "appnexus" }, "targetbiddercode": "appnexus", "type": "banner", @@ -435,7 +424,6 @@ "origbidcpm": 0.3, "prebid": { "meta": { - "adaptercode": "appnexus" }, "type": "banner" } diff --git a/exchange/exchangetest/multi-bids-with-extra-bids.json b/exchange/exchangetest/multi-bids-with-extra-bids.json index 92a7ff0ae73..4c87eb91cb9 100644 --- a/exchange/exchangetest/multi-bids-with-extra-bids.json +++ b/exchange/exchangetest/multi-bids-with-extra-bids.json @@ -174,7 +174,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } }, { @@ -188,7 +187,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } }, { @@ -202,7 +200,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } }, { @@ -216,7 +213,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } } ], @@ -293,7 +289,6 @@ }, "bidType": "banner", "bidMeta": { - "adaptercode": "appnexus" } }, { @@ -307,7 +302,6 @@ }, "bidType": "banner", "bidMeta": { - "adaptercode": "appnexus" } } ], @@ -335,7 +329,6 @@ "origbidcpm": 0.71, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video", "targeting": { @@ -365,7 +358,6 @@ "origbidcpm": 0.21, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video" } @@ -382,7 +374,6 @@ "origbidcpm": 0.61, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video", "targeting": { @@ -412,7 +403,6 @@ "origbidcpm": 0.51, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video", "targeting": { @@ -442,7 +432,6 @@ "origbidcpm": 0.3, "prebid": { "meta": { - "adaptercode": "appnexus" }, "targetbiddercode": "appnexus", "type": "banner", @@ -467,7 +456,6 @@ "origbidcpm": 0.3, "prebid": { "meta": { - "adaptercode": "appnexus" }, "type": "banner" } diff --git a/exchange/exchangetest/multi-bids.json b/exchange/exchangetest/multi-bids.json index ed3e1c1d423..b4e02b57e64 100644 --- a/exchange/exchangetest/multi-bids.json +++ b/exchange/exchangetest/multi-bids.json @@ -146,7 +146,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } }, { @@ -160,7 +159,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } }, { @@ -174,7 +172,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } }, { @@ -188,7 +185,6 @@ }, "bidType": "video", "bidMeta": { - "adaptercode": "pubmatic" } } ], @@ -261,7 +257,6 @@ }, "bidType": "banner", "bidMeta": { - "adaptercode": "appnexus" } }, { @@ -275,7 +270,6 @@ }, "bidType": "banner", "bidMeta": { - "adaptercode": "appnexus" } } ], @@ -303,7 +297,6 @@ "origbidcpm": 0.71, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video", "targeting": { @@ -333,7 +326,6 @@ "origbidcpm": 0.21, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video" } @@ -350,7 +342,6 @@ "origbidcpm": 0.61, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video", "targeting": { @@ -380,7 +371,6 @@ "origbidcpm": 0.51, "prebid": { "meta": { - "adaptercode": "pubmatic" }, "type": "video", "targeting": { @@ -410,7 +400,6 @@ "origbidcpm": 0.3, "prebid": { "meta": { - "adaptercode": "appnexus" }, "targetbiddercode": "appnexus", "type": "banner", @@ -435,7 +424,6 @@ "origbidcpm": 0.3, "prebid": { "meta": { - "adaptercode": "appnexus" }, "type": "banner" } diff --git a/exchange/exchangetest/passthrough_imp_only.json b/exchange/exchangetest/passthrough_imp_only.json index b7b2270270b..a577f73c862 100644 --- a/exchange/exchangetest/passthrough_imp_only.json +++ b/exchange/exchangetest/passthrough_imp_only.json @@ -144,6 +144,8 @@ "someField": "someValue", "origbidcpm": 0.3, "prebid": { + "meta": { + }, "type": "video", "passthrough": { "imp_passthrough_val": 20 @@ -162,6 +164,8 @@ "someField": "someValue", "origbidcpm": 0.3, "prebid": { + "meta": { + }, "type": "video" } } diff --git a/exchange/exchangetest/passthrough_root_and_imp.json b/exchange/exchangetest/passthrough_root_and_imp.json index 7d4fe89b6d2..d8a0505582b 100644 --- a/exchange/exchangetest/passthrough_root_and_imp.json +++ b/exchange/exchangetest/passthrough_root_and_imp.json @@ -106,6 +106,8 @@ "someField": "someValue", "origbidcpm": 0.3, "prebid": { + "meta": { + }, "type": "video", "passthrough": { "imp_passthrough_val": 20 diff --git a/exchange/exchangetest/passthrough_root_only.json b/exchange/exchangetest/passthrough_root_only.json index 0b444535348..fe922431f76 100644 --- a/exchange/exchangetest/passthrough_root_only.json +++ b/exchange/exchangetest/passthrough_root_only.json @@ -101,6 +101,8 @@ "crid": "creative-1", "ext": { "prebid": { + "meta": { + }, "type": "video" }, "someField": "someValue", diff --git a/exchange/exchangetest/request-ext-prebid-filtering.json b/exchange/exchangetest/request-ext-prebid-filtering.json index 8e85d8edfdc..26312fbf938 100644 --- a/exchange/exchangetest/request-ext-prebid-filtering.json +++ b/exchange/exchangetest/request-ext-prebid-filtering.json @@ -174,6 +174,8 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + }, "type": "video" } } diff --git a/exchange/exchangetest/request-imp-ext-prebid-filtering.json b/exchange/exchangetest/request-imp-ext-prebid-filtering.json index fb8e0892aa5..8733ef9c074 100644 --- a/exchange/exchangetest/request-imp-ext-prebid-filtering.json +++ b/exchange/exchangetest/request-imp-ext-prebid-filtering.json @@ -124,6 +124,8 @@ "ext": { "origbidcpm": 0.3, "prebid": { + "meta": { + }, "type": "video" } } diff --git a/exchange/exchangetest/request-multi-bidders-debug-info.json b/exchange/exchangetest/request-multi-bidders-debug-info.json index 8f41286b3eb..254d321f373 100644 --- a/exchange/exchangetest/request-multi-bidders-debug-info.json +++ b/exchange/exchangetest/request-multi-bidders-debug-info.json @@ -154,6 +154,8 @@ "ext": { "origbidcpm": 12.00, "prebid": { + "meta": { + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/request-multi-bidders-one-no-resp.json b/exchange/exchangetest/request-multi-bidders-one-no-resp.json index c1fe595ad5f..4d54abc2c97 100644 --- a/exchange/exchangetest/request-multi-bidders-one-no-resp.json +++ b/exchange/exchangetest/request-multi-bidders-one-no-resp.json @@ -126,6 +126,8 @@ "ext": { "origbidcpm": 12.00, "prebid": { + "meta": { + }, "targeting": { "hb_bidder": "appnexus", "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/schain-host-and-request.json b/exchange/exchangetest/schain-host-and-request.json index 5bfcd1c3527..0552bb09b30 100644 --- a/exchange/exchangetest/schain-host-and-request.json +++ b/exchange/exchangetest/schain-host-and-request.json @@ -1,5 +1,8 @@ { "host_schain_flag": true, + "ortbversion": { + "appnexus":"2.6" + }, "incomingRequest": { "ortbRequest": { "id": "some-request-id", @@ -84,25 +87,23 @@ } }, "source": { - "ext": { - "schain": { - "complete": 1, - "nodes": [ - { - "asi": "whatever.com", - "sid": "1234", - "rid": "123-456-7890", - "hp": 1 - }, - { - "asi": "pbshostcompany.com", - "sid": "00001", - "rid": "BidRequest", - "hp": 1 - } - ], - "ver": "2.0" - } + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "whatever.com", + "sid": "1234", + "rid": "123-456-7890", + "hp": 1 + }, + { + "asi": "pbshostcompany.com", + "sid": "00001", + "rid": "BidRequest", + "hp": 1 + } + ], + "ver": "2.0" } } }, diff --git a/exchange/exchangetest/schain-host-only.json b/exchange/exchangetest/schain-host-only.json index 0cc077e2fdb..89fb70c3676 100644 --- a/exchange/exchangetest/schain-host-only.json +++ b/exchange/exchangetest/schain-host-only.json @@ -1,5 +1,8 @@ { "host_schain_flag": true, + "ortbversion": { + "appnexus":"2.6" + }, "incomingRequest": { "ortbRequest": { "id": "some-request-id", @@ -64,19 +67,17 @@ } }, "source": { - "ext": { - "schain": { - "complete": 0, - "nodes": [ - { - "asi": "pbshostcompany.com", - "sid": "00001", - "rid": "BidRequest", - "hp": 1 - } - ], - "ver": "1.0" - } + "schain": { + "complete": 0, + "nodes": [ + { + "asi": "pbshostcompany.com", + "sid": "00001", + "rid": "BidRequest", + "hp": 1 + } + ], + "ver": "1.0" } } }, diff --git a/exchange/exchangetest/targeting-always-include-deals.json b/exchange/exchangetest/targeting-always-include-deals.json new file mode 100644 index 00000000000..145210369e2 --- /dev/null +++ b/exchange/exchangetest/targeting-always-include-deals.json @@ -0,0 +1,291 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "audienceNetwork": { + "placementId": "some-placement" + } + } + } + } + }, + { + "id": "imp-id-2", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 2 + }, + "audienceNetwork": { + "placementId": "some-other-placement" + } + } + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "min": 0, + "max": 20, + "increment": 0.1 + } + ] + }, + "includewinners": true, + "includebidderkeys": false, + "alwaysincludedeals": true + } + } + } + } + }, + "outgoingRequests": { + "appnexus": { + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1", + "dealid": "deal-1" + }, + "bidType": "video" + }, + { + "ortbBid": { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2", + "dealid": "deal-2" + }, + "bidType": "video" + }, + { + "ortbBid": { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3", + "dealid": "deal-3" + }, + "bidType": "video" + } + ], + "seat": "appnexus" + } + ] + } + }, + "audienceNetwork": { + "mockResponse": { + "pbsSeatBids": [ + { + "pbsBids": [ + { + "ortbBid": { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4", + "dealid": "deal-4" + }, + "bidType": "video" + }, + { + "ortbBid": { + "id": "losing-bid-aN", + "impid": "imp-id-2", + "price": 0.40, + "w": 200, + "h": 250, + "crid": "creative-5" + }, + "bidType": "video" + } + ], + "seat": "audienceNetwork" + } + ] + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [ + { + "seat": "audienceNetwork", + "bid": [ + { + "id": "contending-bid", + "impid": "my-imp-id", + "price": 0.51, + "w": 200, + "h": 250, + "crid": "creative-4", + "dealid": "deal-4", + "ext": { + "origbidcpm": 0.51, + "prebid": { + "meta": { + }, + "type": "video", + "targeting": { + "hb_bidder_audienceNe": "audienceNetwork", + "hb_cache_host_audien": "www.pbcserver.com", + "hb_cache_path_audien": "/pbcache/endpoint", + "hb_deal_audienceNetw": "deal-4", + "hb_pb_audienceNetwor": "0.50", + "hb_size_audienceNetw": "200x250" + } + } + } + }, + { + "id": "losing-bid-aN", + "impid": "imp-id-2", + "price": 0.40, + "w": 200, + "h": 250, + "crid": "creative-5", + "ext": { + "origbidcpm": 0.40, + "prebid": { + "meta": { + }, + "type": "video" + } + } + } + ] + }, + { + "seat": "appnexus", + "bid": [ + { + "id": "winning-bid", + "impid": "my-imp-id", + "price": 0.71, + "w": 200, + "h": 250, + "crid": "creative-1", + "dealid": "deal-1", + "ext": { + "origbidcpm": 0.71, + "prebid": { + "meta": { + }, + "type": "video", + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "0.70", + "hb_pb_appnexus": "0.70", + "hb_deal":"deal-1", + "hb_deal_appnexus":"deal-1", + "hb_size": "200x250", + "hb_size_appnexus": "200x250" + } + } + } + }, + { + "id": "losing-bid", + "impid": "my-imp-id", + "price": 0.21, + "w": 200, + "h": 250, + "crid": "creative-2", + "dealid": "deal-2", + "ext": { + "origbidcpm": 0.21, + "prebid": { + "meta": { + }, + "type": "video" + } + } + }, + { + "id": "other-bid", + "impid": "imp-id-2", + "price": 0.61, + "w": 300, + "h": 500, + "crid": "creative-3", + "dealid": "deal-3", + "ext": { + "origbidcpm": 0.61, + "prebid": { + "meta": { + }, + "type": "video", + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_deal":"deal-3", + "hb_deal_appnexus":"deal-3", + "hb_pb": "0.60", + "hb_pb_appnexus": "0.60", + "hb_size": "300x500", + "hb_size_appnexus": "300x500" + } + } + } + } + ] + } + ] + } + } +} diff --git a/exchange/exchangetest/targeting-cache-vast-banner.json b/exchange/exchangetest/targeting-cache-vast-banner.json index 43b65e59c12..66f322e4cbb 100644 --- a/exchange/exchangetest/targeting-cache-vast-banner.json +++ b/exchange/exchangetest/targeting-cache-vast-banner.json @@ -87,6 +87,8 @@ "ext": { "origbidcpm": 0.01, "prebid": { + "meta": { + }, "type": "banner", "targeting": { "hb_bidder": "appnexus", diff --git a/exchange/exchangetest/targeting-cache-vast.json b/exchange/exchangetest/targeting-cache-vast.json index 2f59ba30878..5e1aca02813 100644 --- a/exchange/exchangetest/targeting-cache-vast.json +++ b/exchange/exchangetest/targeting-cache-vast.json @@ -88,6 +88,8 @@ "ext": { "origbidcpm": 0.01, "prebid": { + "meta": { + }, "cache": { "bids": { "cacheId": "0", diff --git a/exchange/exchangetest/targeting-cache-zero.json b/exchange/exchangetest/targeting-cache-zero.json index ea062fbe277..ff9672c3562 100644 --- a/exchange/exchangetest/targeting-cache-zero.json +++ b/exchange/exchangetest/targeting-cache-zero.json @@ -90,6 +90,8 @@ "ext": { "origbidcpm": 0.01, "prebid": { + "meta": { + }, "cache": { "bids": { "cacheId": "0", diff --git a/exchange/exchangetest/targeting-mobile.json b/exchange/exchangetest/targeting-mobile.json index 914a557bca9..3ae2a0ae476 100644 --- a/exchange/exchangetest/targeting-mobile.json +++ b/exchange/exchangetest/targeting-mobile.json @@ -152,6 +152,8 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + }, "type": "video", "targeting": { "hb_bidder_audienceNe": "audienceNetwork", @@ -179,6 +181,8 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -207,6 +211,8 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + }, "type": "video" } } @@ -221,6 +227,8 @@ "ext": { "origbidcpm": 0.61, "prebid": { + "meta": { + }, "type": "video", "targeting": { "hb_bidder": "appnexus", diff --git a/exchange/exchangetest/targeting-no-winners.json b/exchange/exchangetest/targeting-no-winners.json index 6ab69fb4773..e6400d14236 100644 --- a/exchange/exchangetest/targeting-no-winners.json +++ b/exchange/exchangetest/targeting-no-winners.json @@ -152,6 +152,8 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + }, "type": "video", "targeting": { "hb_bidder_audienceNe": "audienceNetwork", @@ -178,6 +180,8 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + }, "type": "video", "targeting": { "hb_bidder_appnexus": "appnexus", @@ -199,6 +203,8 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + }, "type": "video" } } @@ -213,6 +219,8 @@ "ext": { "origbidcpm": 0.61, "prebid": { + "meta": { + }, "type": "video", "targeting": { "hb_bidder_appnexus": "appnexus", diff --git a/exchange/exchangetest/targeting-only-winners.json b/exchange/exchangetest/targeting-only-winners.json index f9f96515452..85bb81ed906 100644 --- a/exchange/exchangetest/targeting-only-winners.json +++ b/exchange/exchangetest/targeting-only-winners.json @@ -152,6 +152,8 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + }, "type": "video" } } @@ -171,6 +173,8 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -192,6 +196,8 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + }, "type": "video" } } @@ -206,6 +212,8 @@ "ext": { "origbidcpm": 0.61, "prebid": { + "meta": { + }, "type": "video", "targeting": { "hb_bidder": "appnexus", diff --git a/exchange/exchangetest/targeting-with-winners.json b/exchange/exchangetest/targeting-with-winners.json index d1a0f49f862..281e95b962b 100644 --- a/exchange/exchangetest/targeting-with-winners.json +++ b/exchange/exchangetest/targeting-with-winners.json @@ -152,6 +152,8 @@ "ext": { "origbidcpm": 0.51, "prebid": { + "meta": { + }, "type": "video", "targeting": { "hb_bidder_audienceNe": "audienceNetwork", @@ -178,6 +180,8 @@ "ext": { "origbidcpm": 0.71, "prebid": { + "meta": { + }, "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -204,6 +208,8 @@ "ext": { "origbidcpm": 0.21, "prebid": { + "meta": { + }, "type": "video" } } @@ -218,6 +224,8 @@ "ext": { "origbidcpm": 0.61, "prebid": { + "meta": { + }, "type": "video", "targeting": { "hb_bidder": "appnexus", diff --git a/exchange/gdpr.go b/exchange/gdpr.go index d503eb5da27..866f33baed2 100644 --- a/exchange/gdpr.go +++ b/exchange/gdpr.go @@ -3,9 +3,9 @@ package exchange import ( gpplib "github.com/prebid/go-gpp" gppConstants "github.com/prebid/go-gpp/constants" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/openrtb_ext" - gppPolicy "github.com/prebid/prebid-server/privacy/gpp" + "github.com/prebid/prebid-server/v3/gdpr" + "github.com/prebid/prebid-server/v3/openrtb_ext" + gppPolicy "github.com/prebid/prebid-server/v3/privacy/gpp" ) // getGDPR will pull the gdpr flag from an openrtb request @@ -16,11 +16,11 @@ func getGDPR(req *openrtb_ext.RequestWrapper) (gdpr.Signal, error) { } return gdpr.SignalNo, nil } - re, err := req.GetRegExt() - if re == nil || re.GetGDPR() == nil || err != nil { - return gdpr.SignalAmbiguous, err + if req.Regs != nil && req.Regs.GDPR != nil { + return gdpr.IntSignalParse(int(*req.Regs.GDPR)) } - return gdpr.Signal(*re.GetGDPR()), nil + return gdpr.SignalAmbiguous, nil + } // getConsent will pull the consent string from an openrtb request @@ -29,9 +29,14 @@ func getConsent(req *openrtb_ext.RequestWrapper, gpp gpplib.GppContainer) (conse consent = gpp.Sections[i].GetValue() return } - ue, err := req.GetUserExt() - if ue == nil || ue.GetConsent() == nil || err != nil { - return + if req.User != nil { + return req.User.Consent, nil } - return *ue.GetConsent(), nil + return +} + +// enforceGDPR determines if GDPR should be enforced based on the request signal and whether the channel is enabled +func enforceGDPR(signal gdpr.Signal, defaultValue gdpr.Signal, channelEnabled bool) bool { + gdprApplies := signal == gdpr.SignalYes || (signal == gdpr.SignalAmbiguous && defaultValue == gdpr.SignalYes) + return gdprApplies && channelEnabled } diff --git a/exchange/gdpr_test.go b/exchange/gdpr_test.go index 44573b59167..b2d175c2ad0 100644 --- a/exchange/gdpr_test.go +++ b/exchange/gdpr_test.go @@ -1,14 +1,14 @@ package exchange import ( - "encoding/json" "testing" gpplib "github.com/prebid/go-gpp" gppConstants "github.com/prebid/go-gpp/constants" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/gdpr" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -21,17 +21,17 @@ func TestGetGDPR(t *testing.T) { }{ { description: "Regs Ext GDPR = 0", - giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": 0}`)}, + giveRegs: &openrtb2.Regs{GDPR: ptrutil.ToPtr[int8](0)}, wantGDPR: gdpr.SignalNo, }, { description: "Regs Ext GDPR = 1", - giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": 1}`)}, + giveRegs: &openrtb2.Regs{GDPR: ptrutil.ToPtr[int8](1)}, wantGDPR: gdpr.SignalYes, }, { description: "Regs Ext GDPR = null", - giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": null}`)}, + giveRegs: &openrtb2.Regs{GDPR: nil}, wantGDPR: gdpr.SignalAmbiguous, }, { @@ -39,30 +39,19 @@ func TestGetGDPR(t *testing.T) { giveRegs: nil, wantGDPR: gdpr.SignalAmbiguous, }, - { - description: "Regs Ext is nil", - giveRegs: &openrtb2.Regs{Ext: nil}, - wantGDPR: gdpr.SignalAmbiguous, - }, - { - description: "JSON unmarshal error", - giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"`)}, - wantGDPR: gdpr.SignalAmbiguous, - wantError: true, - }, { description: "Regs Ext GDPR = null, GPPSID has tcf2", - giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": null}`), GPPSID: []int8{2}}, + giveRegs: &openrtb2.Regs{GDPR: nil, GPPSID: []int8{2}}, wantGDPR: gdpr.SignalYes, }, { description: "Regs Ext GDPR = 1, GPPSID has uspv1", - giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": 1}`), GPPSID: []int8{6}}, + giveRegs: &openrtb2.Regs{GDPR: ptrutil.ToPtr[int8](1), GPPSID: []int8{6}}, wantGDPR: gdpr.SignalNo, }, { description: "Regs Ext GDPR = 0, GPPSID has tcf2", - giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": 0}`), GPPSID: []int8{2}}, + giveRegs: &openrtb2.Regs{GDPR: ptrutil.ToPtr[int8](0), GPPSID: []int8{2}}, wantGDPR: gdpr.SignalYes, }, { @@ -105,18 +94,13 @@ func TestGetConsent(t *testing.T) { wantError bool }{ { - description: "User Ext Consent is not empty", - giveUser: &openrtb2.User{Ext: json.RawMessage(`{"consent": "BOS2bx5OS2bx5ABABBAAABoAAAAAFA"}`)}, + description: "User Consent is not empty", + giveUser: &openrtb2.User{Consent: "BOS2bx5OS2bx5ABABBAAABoAAAAAFA"}, wantConsent: "BOS2bx5OS2bx5ABABBAAABoAAAAAFA", }, { - description: "User Ext Consent is empty", - giveUser: &openrtb2.User{Ext: json.RawMessage(`{"consent": ""}`)}, - wantConsent: "", - }, - { - description: "User Ext is nil", - giveUser: &openrtb2.User{Ext: nil}, + description: "User Consent is empty", + giveUser: &openrtb2.User{Consent: ""}, wantConsent: "", }, { @@ -125,26 +109,20 @@ func TestGetConsent(t *testing.T) { wantConsent: "", }, { - description: "JSON unmarshal error", - giveUser: &openrtb2.User{Ext: json.RawMessage(`{`)}, - wantConsent: "", - wantError: true, - }, - { - description: "User Ext is nil, GPP has no GDPR", - giveUser: &openrtb2.User{Ext: nil}, + description: "User is nil, GPP has no GDPR", + giveUser: nil, giveGPP: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{6}, Sections: []gpplib.Section{&upsv1Section}}, wantConsent: "", }, { - description: "User Ext is nil, GPP has GDPR", - giveUser: &openrtb2.User{Ext: nil}, + description: "User is nil, GPP has GDPR", + giveUser: nil, giveGPP: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{2}, Sections: []gpplib.Section{&tcf1Section}}, wantConsent: "BOS2bx5OS2bx5ABABBAAABoAAAAAFA", }, { - description: "User Ext has GDPR, GPP has GDPR", - giveUser: &openrtb2.User{Ext: json.RawMessage(`{"consent": "BSOMECONSENT"}`)}, + description: "User has GDPR, GPP has GDPR", + giveUser: &openrtb2.User{Consent: "BSOMECONSENT"}, giveGPP: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{2}, Sections: []gpplib.Section{&tcf1Section}}, wantConsent: "BOS2bx5OS2bx5ABABBAAABoAAAAAFA", }, @@ -185,3 +163,7 @@ func (ms mockGPPSection) GetID() gppConstants.SectionID { func (ms mockGPPSection) GetValue() string { return ms.value } + +func (ms mockGPPSection) Encode(bool) []byte { + return nil +} diff --git a/exchange/non_bid_reason.go b/exchange/non_bid_reason.go index 9a6f1f6bf61..05d4ea3ee66 100644 --- a/exchange/non_bid_reason.go +++ b/exchange/non_bid_reason.go @@ -1,24 +1,57 @@ package exchange +import ( + "errors" + "net" + "syscall" + + "github.com/prebid/prebid-server/v3/errortypes" +) + // SeatNonBid list the reasons why bid was not resulted in positive bid // reason could be either No bid, Error, Request rejection or Response rejection -// Reference: https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/extensions/community_extensions/seat-non-bid.md -type NonBidReason int +// Reference: https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/extensions/community_extensions/seat-non-bid.md#list-non-bid-status-codes +type NonBidReason int64 const ( - NoBidUnknownError NonBidReason = 0 // No Bid - General + ErrorGeneral NonBidReason = 100 // Error - General + ErrorTimeout NonBidReason = 101 // Error - Timeout + ErrorBidderUnreachable NonBidReason = 103 // Error - Bidder Unreachable + ResponseRejectedGeneral NonBidReason = 300 + ResponseRejectedBelowFloor NonBidReason = 301 // Response Rejected - Below Floor ResponseRejectedCategoryMappingInvalid NonBidReason = 303 // Response Rejected - Category Mapping Invalid + ResponseRejectedBelowDealFloor NonBidReason = 304 // Response Rejected - Bid was Below Deal Floor + ResponseRejectedCreativeSizeNotAllowed NonBidReason = 351 // Response Rejected - Invalid Creative (Size Not Allowed) + ResponseRejectedCreativeNotSecure NonBidReason = 352 // Response Rejected - Invalid Creative (Not Secure) ) -// Ptr returns pointer to own value. -func (n NonBidReason) Ptr() *NonBidReason { - return &n +func errorToNonBidReason(err error) NonBidReason { + switch errortypes.ReadCode(err) { + case errortypes.TimeoutErrorCode: + return ErrorTimeout + default: + return ErrorGeneral + } } -// Val safely dereferences pointer, returning default value (NoBidUnknownError) for nil. -func (n *NonBidReason) Val() NonBidReason { - if n == nil { - return NoBidUnknownError +// httpInfoToNonBidReason determines NoBidReason code (NBR) +// It will first try to resolve the NBR based on prebid's proprietary error code. +// If proprietary error code not found then it will try to determine NBR using +// system call level error code +func httpInfoToNonBidReason(httpInfo *httpCallInfo) NonBidReason { + nonBidReason := errorToNonBidReason(httpInfo.err) + if nonBidReason != ErrorGeneral { + return nonBidReason } - return *n + if isBidderUnreachableError(httpInfo) { + return ErrorBidderUnreachable + } + return ErrorGeneral +} + +// isBidderUnreachableError checks if the error is due to connection refused or no such host +func isBidderUnreachableError(httpInfo *httpCallInfo) bool { + var dnsErr *net.DNSError + isNoSuchHost := errors.As(httpInfo.err, &dnsErr) && dnsErr.IsNotFound + return errors.Is(httpInfo.err, syscall.ECONNREFUSED) || isNoSuchHost } diff --git a/exchange/non_bid_reason_test.go b/exchange/non_bid_reason_test.go new file mode 100644 index 00000000000..fb9c5e434fc --- /dev/null +++ b/exchange/non_bid_reason_test.go @@ -0,0 +1,65 @@ +package exchange + +import ( + "errors" + "net" + "syscall" + "testing" + + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/stretchr/testify/assert" +) + +func Test_httpInfoToNonBidReason(t *testing.T) { + type args struct { + httpInfo *httpCallInfo + } + tests := []struct { + name string + args args + want NonBidReason + }{ + { + name: "error-timeout", + args: args{ + httpInfo: &httpCallInfo{ + err: &errortypes.Timeout{}, + }, + }, + want: ErrorTimeout, + }, + { + name: "error-general", + args: args{ + httpInfo: &httpCallInfo{ + err: errors.New("some_error"), + }, + }, + want: ErrorGeneral, + }, + { + name: "error-bidderUnreachable", + args: args{ + httpInfo: &httpCallInfo{ + err: syscall.ECONNREFUSED, + }, + }, + want: ErrorBidderUnreachable, + }, + { + name: "error-biddersUnreachable-no-such-host", + args: args{ + httpInfo: &httpCallInfo{ + err: &net.DNSError{IsNotFound: true}, + }, + }, + want: ErrorBidderUnreachable, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := httpInfoToNonBidReason(tt.args.httpInfo) + assert.Equal(t, tt.want, actual) + }) + } +} diff --git a/exchange/price_granularity.go b/exchange/price_granularity.go index af9e46b20fe..ba62c1aa4ab 100644 --- a/exchange/price_granularity.go +++ b/exchange/price_granularity.go @@ -1,10 +1,11 @@ package exchange import ( - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" "math" "strconv" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // GetPriceBucket is the externally facing function for computing CPM buckets diff --git a/exchange/price_granularity_test.go b/exchange/price_granularity_test.go index 4f9337aadc3..bb0e5e7d73b 100644 --- a/exchange/price_granularity_test.go +++ b/exchange/price_granularity_test.go @@ -5,9 +5,9 @@ import ( "math" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" ) diff --git a/exchange/seat_non_bids.go b/exchange/seat_non_bids.go index 463a4595c85..fd6fd1da3ff 100644 --- a/exchange/seat_non_bids.go +++ b/exchange/seat_non_bids.go @@ -1,26 +1,22 @@ package exchange import ( - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) -type nonBids struct { - seatNonBidsMap map[string][]openrtb_ext.NonBid -} +type SeatNonBidBuilder 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 { +// rejectBid appends a non bid object to the builder based on a bid +func (b SeatNonBidBuilder) rejectBid(bid *entities.PbsOrtbBid, nonBidReason int, seat string) { + if b == nil || bid == nil || bid.Bid == nil { return } - if snb.seatNonBidsMap == nil { - snb.seatNonBidsMap = make(map[string][]openrtb_ext.NonBid) - } + nonBid := openrtb_ext.NonBid{ ImpId: bid.Bid.ImpID, StatusCode: nonBidReason, - Ext: openrtb_ext.NonBidExt{ + Ext: &openrtb_ext.NonBidExt{ Prebid: openrtb_ext.ExtResponseNonBidPrebid{Bid: openrtb_ext.NonBidObject{ Price: bid.Bid.Price, ADomain: bid.Bid.ADomain, @@ -36,16 +32,29 @@ func (snb *nonBids) addBid(bid *entities.PbsOrtbBid, nonBidReason int, seat stri }}, }, } - - snb.seatNonBidsMap[seat] = append(snb.seatNonBidsMap[seat], nonBid) + b[seat] = append(b[seat], nonBid) } -func (snb *nonBids) get() []openrtb_ext.SeatNonBid { - if snb == nil { - return nil +// rejectImps appends a non bid object to the builder for every specified imp +func (b SeatNonBidBuilder) rejectImps(impIds []string, nonBidReason NonBidReason, seat string) { + nonBids := []openrtb_ext.NonBid{} + for _, impId := range impIds { + nonBid := openrtb_ext.NonBid{ + ImpId: impId, + StatusCode: int(nonBidReason), + } + nonBids = append(nonBids, nonBid) } - var seatNonBid []openrtb_ext.SeatNonBid - for seat, nonBids := range snb.seatNonBidsMap { + + if len(nonBids) > 0 { + b[seat] = append(b[seat], nonBids...) + } +} + +// slice transforms the seat non bid map into a slice of SeatNonBid objects representing the non-bids for each seat +func (b SeatNonBidBuilder) Slice() []openrtb_ext.SeatNonBid { + seatNonBid := make([]openrtb_ext.SeatNonBid, 0) + for seat, nonBids := range b { seatNonBid = append(seatNonBid, openrtb_ext.SeatNonBid{ Seat: seat, NonBid: nonBids, @@ -53,3 +62,16 @@ func (snb *nonBids) get() []openrtb_ext.SeatNonBid { } return seatNonBid } + +// append adds the nonBids from the input nonBids to the current nonBids. +// This method is not thread safe as we are initializing and writing to map +func (b SeatNonBidBuilder) append(nonBids ...SeatNonBidBuilder) { + if b == nil { + return + } + for _, nonBid := range nonBids { + for seat, nonBids := range nonBid { + b[seat] = append(b[seat], nonBids...) + } + } +} diff --git a/exchange/seat_non_bids_test.go b/exchange/seat_non_bids_test.go index d9f7aa88ca0..4a8f9e8f784 100644 --- a/exchange/seat_non_bids_test.go +++ b/exchange/seat_non_bids_test.go @@ -3,15 +3,15 @@ package exchange import ( "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) -func TestSeatNonBidsAdd(t *testing.T) { +func TestRejectBid(t *testing.T) { type fields struct { - seatNonBidsMap map[string][]openrtb_ext.NonBid + builder SeatNonBidBuilder } type args struct { bid *entities.PbsOrtbBid @@ -22,89 +22,512 @@ func TestSeatNonBidsAdd(t *testing.T) { name string fields fields args args - want map[string][]openrtb_ext.NonBid + want SeatNonBidBuilder }{ { - name: "nil-seatNonBidsMap", - fields: fields{seatNonBidsMap: nil}, - args: args{}, - want: nil, + name: "nil_builder", + fields: fields{ + builder: 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: "nil_pbsortbid", + fields: fields{ + builder: SeatNonBidBuilder{}, + }, + args: args{ + bid: nil, + }, + want: SeatNonBidBuilder{}, }, { - 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: "nil_bid", + fields: fields{ + builder: SeatNonBidBuilder{}, + }, + args: args{ + bid: &entities.PbsOrtbBid{ + Bid: nil, + }, + }, + want: SeatNonBidBuilder{}, + }, + { + name: "append_nonbids_new_seat", + fields: fields{ + builder: SeatNonBidBuilder{}, + }, + args: args{ + bid: &entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + ImpID: "Imp1", + Price: 10, + }, + }, + nonBidReason: int(ErrorGeneral), + seat: "seat1", + }, + want: SeatNonBidBuilder{ + "seat1": []openrtb_ext.NonBid{ + { + ImpId: "Imp1", + StatusCode: int(ErrorGeneral), + Ext: &openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + Price: 10, + }, + }, + }, + }, + }, + }, + }, + { + name: "append_nonbids_for_different_seat", + fields: fields{ + builder: SeatNonBidBuilder{ + "seat1": []openrtb_ext.NonBid{ + { + ImpId: "Imp1", + StatusCode: int(ErrorGeneral), + }, + }, + }, + }, + args: args{ + bid: &entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + ImpID: "Imp2", + Price: 10, + }, + }, + nonBidReason: int(ErrorGeneral), + seat: "seat2", + }, + want: SeatNonBidBuilder{ + "seat1": []openrtb_ext.NonBid{ + { + ImpId: "Imp1", + StatusCode: int(ErrorGeneral), + }, + }, + "seat2": []openrtb_ext.NonBid{ + { + ImpId: "Imp2", + StatusCode: int(ErrorGeneral), + Ext: &openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + Price: 10, + }, + }, + }, + }, + }, + }, + }, + { + name: "append_nonbids_for_existing_seat", + fields: fields{ + builder: SeatNonBidBuilder{ + "seat1": []openrtb_ext.NonBid{ + { + ImpId: "Imp1", + StatusCode: int(ErrorGeneral), + }, + }, + }, + }, + args: args{ + bid: &entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + ImpID: "Imp2", + Price: 10, + }, + }, + nonBidReason: int(ErrorGeneral), + seat: "seat1", + }, + want: SeatNonBidBuilder{ + "seat1": []openrtb_ext.NonBid{ + { + ImpId: "Imp1", + StatusCode: int(ErrorGeneral), + }, + { + ImpId: "Imp2", + StatusCode: int(ErrorGeneral), + Ext: &openrtb_ext.NonBidExt{ + Prebid: openrtb_ext.ExtResponseNonBidPrebid{ + Bid: openrtb_ext.NonBidObject{ + Price: 10, + }, + }, + }, + }, + }, + }, }, } 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") + snb := tt.fields.builder + snb.rejectBid(tt.args.bid, tt.args.nonBidReason, tt.args.seat) + assert.Equal(t, tt.want, snb) }) } } -func TestSeatNonBidsGet(t *testing.T) { - type fields struct { - snb *nonBids - } +func TestAppend(t *testing.T) { tests := []struct { - name string - fields fields - want []openrtb_ext.SeatNonBid + name string + builder SeatNonBidBuilder + toAppend []SeatNonBidBuilder + expected SeatNonBidBuilder }{ { - name: "get-seat-nonbids", - fields: fields{&nonBids{sampleSeatNonBidMap("bidder1", 2)}}, - want: sampleSeatBids("bidder1", 2), + name: "nil_buider", + builder: nil, + toAppend: []SeatNonBidBuilder{{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}}}, + expected: nil, + }, + { + name: "empty_builder", + builder: SeatNonBidBuilder{}, + toAppend: []SeatNonBidBuilder{{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}}}, + expected: SeatNonBidBuilder{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}}, + }, + { + name: "append_one_different_seat", + builder: SeatNonBidBuilder{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}}, + toAppend: []SeatNonBidBuilder{{"seat2": []openrtb_ext.NonBid{{ImpId: "imp2"}}}}, + expected: SeatNonBidBuilder{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}, "seat2": []openrtb_ext.NonBid{{ImpId: "imp2"}}}, + }, + { + name: "append_multiple_different_seats", + builder: SeatNonBidBuilder{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}}, + toAppend: []SeatNonBidBuilder{{"seat2": []openrtb_ext.NonBid{{ImpId: "imp2"}}}, {"seat3": []openrtb_ext.NonBid{{ImpId: "imp3"}}}}, + expected: SeatNonBidBuilder{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}, "seat2": []openrtb_ext.NonBid{{ImpId: "imp2"}}, "seat3": []openrtb_ext.NonBid{{ImpId: "imp3"}}}, + }, + { + name: "nil_append", + builder: SeatNonBidBuilder{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}}, + toAppend: nil, + expected: SeatNonBidBuilder{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}}, + }, + { + name: "empty_append", + builder: SeatNonBidBuilder{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}}, + toAppend: []SeatNonBidBuilder{}, + expected: SeatNonBidBuilder{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}}, }, { - name: "nil-seat-nonbids", - fields: fields{nil}, + name: "append_multiple_same_seat", + builder: SeatNonBidBuilder{ + "seat1": []openrtb_ext.NonBid{ + {ImpId: "imp1"}, + }, + }, + toAppend: []SeatNonBidBuilder{ + { + "seat1": []openrtb_ext.NonBid{ + {ImpId: "imp2"}, + }, + }, + }, + expected: SeatNonBidBuilder{ + "seat1": []openrtb_ext.NonBid{ + {ImpId: "imp1"}, + {ImpId: "imp2"}, + }, + }, }, } 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) - } + tt.builder.append(tt.toAppend...) + assert.Equal(t, tt.expected, tt.builder) }) } } -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{}}}, - }) +func TestRejectImps(t *testing.T) { + tests := []struct { + name string + impIDs []string + builder SeatNonBidBuilder + want SeatNonBidBuilder + }{ + { + name: "nil_imps", + impIDs: nil, + builder: SeatNonBidBuilder{}, + want: SeatNonBidBuilder{}, + }, + { + name: "empty_imps", + impIDs: []string{}, + builder: SeatNonBidBuilder{}, + want: SeatNonBidBuilder{}, + }, + { + name: "one_imp", + impIDs: []string{"imp1"}, + builder: SeatNonBidBuilder{}, + want: SeatNonBidBuilder{ + "seat1": []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 300, + }, + }, + }, + }, + { + name: "many_imps", + impIDs: []string{"imp1", "imp2"}, + builder: SeatNonBidBuilder{}, + want: SeatNonBidBuilder{ + "seat1": []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 300, + }, + { + ImpId: "imp2", + StatusCode: 300, + }, + }, + }, + }, + { + name: "many_imps_appended_to_prepopulated_list", + impIDs: []string{"imp1", "imp2"}, + builder: SeatNonBidBuilder{ + "seat0": []openrtb_ext.NonBid{ + { + ImpId: "imp0", + StatusCode: 0, + }, + }, + }, + want: SeatNonBidBuilder{ + "seat0": []openrtb_ext.NonBid{ + { + ImpId: "imp0", + StatusCode: 0, + }, + }, + "seat1": []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 300, + }, + { + ImpId: "imp2", + StatusCode: 300, + }, + }, + }, + }, + { + name: "many_imps_appended_to_prepopulated_list_same_seat", + impIDs: []string{"imp1", "imp2"}, + builder: SeatNonBidBuilder{ + "seat1": []openrtb_ext.NonBid{ + { + ImpId: "imp0", + StatusCode: 300, + }, + }, + }, + want: SeatNonBidBuilder{ + "seat1": []openrtb_ext.NonBid{ + { + ImpId: "imp0", + StatusCode: 300, + }, + { + ImpId: "imp1", + StatusCode: 300, + }, + { + ImpId: "imp2", + StatusCode: 300, + }, + }, + }, + }, } - return map[string][]openrtb_ext.NonBid{ - seat: nonBids, + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.builder.rejectImps(test.impIDs, 300, "seat1") + + assert.Equal(t, len(test.builder), len(test.want)) + for seat := range test.want { + assert.ElementsMatch(t, test.want[seat], test.builder[seat]) + } + }) } } -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), +func TestSlice(t *testing.T) { + tests := []struct { + name string + builder SeatNonBidBuilder + want []openrtb_ext.SeatNonBid + }{ + { + name: "nil", + builder: nil, + want: []openrtb_ext.SeatNonBid{}, + }, + { + name: "empty", + builder: SeatNonBidBuilder{}, + want: []openrtb_ext.SeatNonBid{}, + }, + { + name: "one_no_nonbids", + builder: SeatNonBidBuilder{ + "a": []openrtb_ext.NonBid{}, + }, + want: []openrtb_ext.SeatNonBid{ + { + NonBid: []openrtb_ext.NonBid{}, + Seat: "a", + }, + }, + }, + { + name: "one_with_nonbids", + builder: SeatNonBidBuilder{ + "a": []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 100, + }, + { + ImpId: "imp2", + StatusCode: 200, + }, + }, + }, + want: []openrtb_ext.SeatNonBid{ + { + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 100, + }, + { + ImpId: "imp2", + StatusCode: 200, + }, + }, + Seat: "a", + }, + }, + }, + { + name: "many_no_nonbids", + builder: SeatNonBidBuilder{ + "a": []openrtb_ext.NonBid{}, + "b": []openrtb_ext.NonBid{}, + "c": []openrtb_ext.NonBid{}, + }, + want: []openrtb_ext.SeatNonBid{ + { + NonBid: []openrtb_ext.NonBid{}, + Seat: "a", + }, + { + NonBid: []openrtb_ext.NonBid{}, + Seat: "b", + }, + { + NonBid: []openrtb_ext.NonBid{}, + Seat: "c", + }, + }, + }, + { + name: "many_with_nonbids", + builder: SeatNonBidBuilder{ + "a": []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 100, + }, + { + ImpId: "imp2", + StatusCode: 200, + }, + }, + "b": []openrtb_ext.NonBid{ + { + ImpId: "imp3", + StatusCode: 300, + }, + }, + "c": []openrtb_ext.NonBid{ + { + ImpId: "imp4", + StatusCode: 400, + }, + { + ImpId: "imp5", + StatusCode: 500, + }, + }, + }, + want: []openrtb_ext.SeatNonBid{ + { + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp1", + StatusCode: 100, + }, + { + ImpId: "imp2", + StatusCode: 200, + }, + }, + Seat: "a", + }, + { + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp3", + StatusCode: 300, + }, + }, + Seat: "b", + }, + { + NonBid: []openrtb_ext.NonBid{ + { + ImpId: "imp4", + StatusCode: 400, + }, + { + ImpId: "imp5", + StatusCode: 500, + }, + }, + Seat: "c", + }, + }, + }, } - 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{}}}, + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := test.builder.Slice() + assert.ElementsMatch(t, test.want, result) }) } - seatNonBids = append(seatNonBids, seatNonBid) - return seatNonBids } diff --git a/exchange/targeting.go b/exchange/targeting.go index dbbf10041c9..4f48d70c164 100644 --- a/exchange/targeting.go +++ b/exchange/targeting.go @@ -4,8 +4,8 @@ import ( "fmt" "strconv" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) const MaxKeyLength = 20 @@ -26,6 +26,7 @@ type targetData struct { includeCacheVast bool includeFormat bool preferDeals bool + alwaysIncludeDeals bool // cacheHost and cachePath exist to supply cache host and path as targeting parameters cacheHost string cachePath string @@ -38,7 +39,7 @@ type targetData struct { // it's ok if those stay in the auction. For now, this method implements a very naive cache strategy. // In the future, we should implement a more clever retry & backoff strategy to balance the success rate & performance. func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMapping map[string]string, truncateTargetAttr *int, multiBidMap map[string]openrtb_ext.ExtMultiBid) { - for impId, topBidsPerImp := range auc.winningBidsByBidder { + for impId, topBidsPerImp := range auc.allBidsByBidder { overallWinner := auc.winningBids[impId] for originalBidderName, topBidsPerBidder := range topBidsPerImp { targetingBidderCode := originalBidderName @@ -61,40 +62,42 @@ func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMappi isOverallWinner := overallWinner == topBid + bidHasDeal := len(topBid.Bid.DealID) > 0 + targets := make(map[string]string, 10) if cpm, ok := auc.roundedPrices[topBid]; ok { - targData.addKeys(targets, openrtb_ext.HbpbConstantKey, cpm, targetingBidderCode, isOverallWinner, truncateTargetAttr) + targData.addKeys(targets, openrtb_ext.HbpbConstantKey, cpm, targetingBidderCode, isOverallWinner, truncateTargetAttr, bidHasDeal) } - targData.addKeys(targets, openrtb_ext.HbBidderConstantKey, string(targetingBidderCode), targetingBidderCode, isOverallWinner, truncateTargetAttr) + targData.addKeys(targets, openrtb_ext.HbBidderConstantKey, string(targetingBidderCode), targetingBidderCode, isOverallWinner, truncateTargetAttr, bidHasDeal) if hbSize := makeHbSize(topBid.Bid); hbSize != "" { - targData.addKeys(targets, openrtb_ext.HbSizeConstantKey, hbSize, targetingBidderCode, isOverallWinner, truncateTargetAttr) + targData.addKeys(targets, openrtb_ext.HbSizeConstantKey, hbSize, targetingBidderCode, isOverallWinner, truncateTargetAttr, bidHasDeal) } if cacheID, ok := auc.cacheIds[topBid.Bid]; ok { - targData.addKeys(targets, openrtb_ext.HbCacheKey, cacheID, targetingBidderCode, isOverallWinner, truncateTargetAttr) + targData.addKeys(targets, openrtb_ext.HbCacheKey, cacheID, targetingBidderCode, isOverallWinner, truncateTargetAttr, bidHasDeal) } if vastID, ok := auc.vastCacheIds[topBid.Bid]; ok { - targData.addKeys(targets, openrtb_ext.HbVastCacheKey, vastID, targetingBidderCode, isOverallWinner, truncateTargetAttr) + targData.addKeys(targets, openrtb_ext.HbVastCacheKey, vastID, targetingBidderCode, isOverallWinner, truncateTargetAttr, bidHasDeal) } if targData.includeFormat { - targData.addKeys(targets, openrtb_ext.HbFormatKey, string(topBid.BidType), targetingBidderCode, isOverallWinner, truncateTargetAttr) + targData.addKeys(targets, openrtb_ext.HbFormatKey, string(topBid.BidType), targetingBidderCode, isOverallWinner, truncateTargetAttr, bidHasDeal) } if targData.cacheHost != "" { - targData.addKeys(targets, openrtb_ext.HbConstantCacheHostKey, targData.cacheHost, targetingBidderCode, isOverallWinner, truncateTargetAttr) + targData.addKeys(targets, openrtb_ext.HbConstantCacheHostKey, targData.cacheHost, targetingBidderCode, isOverallWinner, truncateTargetAttr, bidHasDeal) } if targData.cachePath != "" { - targData.addKeys(targets, openrtb_ext.HbConstantCachePathKey, targData.cachePath, targetingBidderCode, isOverallWinner, truncateTargetAttr) + targData.addKeys(targets, openrtb_ext.HbConstantCachePathKey, targData.cachePath, targetingBidderCode, isOverallWinner, truncateTargetAttr, bidHasDeal) } - if deal := topBid.Bid.DealID; len(deal) > 0 { - targData.addKeys(targets, openrtb_ext.HbDealIDConstantKey, deal, targetingBidderCode, isOverallWinner, truncateTargetAttr) + if bidHasDeal { + targData.addKeys(targets, openrtb_ext.HbDealIDConstantKey, topBid.Bid.DealID, targetingBidderCode, isOverallWinner, truncateTargetAttr, bidHasDeal) } if isApp { - targData.addKeys(targets, openrtb_ext.HbEnvKey, openrtb_ext.HbEnvKeyApp, targetingBidderCode, isOverallWinner, truncateTargetAttr) + targData.addKeys(targets, openrtb_ext.HbEnvKey, openrtb_ext.HbEnvKeyApp, targetingBidderCode, isOverallWinner, truncateTargetAttr, bidHasDeal) } if len(categoryMapping) > 0 { - targData.addKeys(targets, openrtb_ext.HbCategoryDurationKey, categoryMapping[topBid.Bid.ID], targetingBidderCode, isOverallWinner, truncateTargetAttr) + targData.addKeys(targets, openrtb_ext.HbCategoryDurationKey, categoryMapping[topBid.Bid.ID], targetingBidderCode, isOverallWinner, truncateTargetAttr, bidHasDeal) } topBid.BidTargets = targets } @@ -102,7 +105,7 @@ func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMappi } } -func (targData *targetData) addKeys(keys map[string]string, key openrtb_ext.TargetingKey, value string, bidderName openrtb_ext.BidderName, overallWinner bool, truncateTargetAttr *int) { +func (targData *targetData) addKeys(keys map[string]string, key openrtb_ext.TargetingKey, value string, bidderName openrtb_ext.BidderName, overallWinner bool, truncateTargetAttr *int, bidHasDeal bool) { var maxLength int if truncateTargetAttr != nil { maxLength = *truncateTargetAttr @@ -112,7 +115,7 @@ func (targData *targetData) addKeys(keys map[string]string, key openrtb_ext.Targ } else { maxLength = MaxKeyLength } - if targData.includeBidderKeys { + if targData.includeBidderKeys || (targData.alwaysIncludeDeals && bidHasDeal) { keys[key.BidderKey(bidderName, maxLength)] = value } if targData.includeWinners && overallWinner { diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index 25c6ea1714a..8bfe67475d2 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -8,18 +8,18 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks/hookexecution" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" - - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/gdpr" + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + metricsConfig "github.com/prebid/prebid-server/v3/metrics/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" + + "github.com/prebid/openrtb/v20/openrtb2" "github.com/stretchr/testify/assert" ) @@ -102,7 +102,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), gdprDefaultValue: gdpr.SignalYes, categoriesFetcher: categoriesFetcher, - bidIDGenerator: &mockBidIDGenerator{false, false}, + bidIDGenerator: &fakeBidIDGenerator{GenerateBidID: false, ReturnError: false}, } ex.requestSplitter = requestSplitter{ me: ex.me, @@ -319,6 +319,11 @@ var bid2p166 *openrtb2.Bid = &openrtb2.Bid{ Price: 1.66, } +var bid175 *openrtb2.Bid = &openrtb2.Bid{ + Price: 1.75, + DealID: "mydeal2", +} + var ( truncateTargetAttrValue10 int = 10 truncateTargetAttrValue5 int = 5 @@ -339,7 +344,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{ includeWinners: true, }, Auction: auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "ImpId-1": { openrtb_ext.BidderAppnexus: {{ Bid: bid123, @@ -374,7 +379,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{ includeBidderKeys: true, }, Auction: auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "ImpId-1": { openrtb_ext.BidderAppnexus: {{ Bid: bid123, @@ -409,6 +414,54 @@ var TargetingTests []TargetingTestData = []TargetingTestData{ }, TruncateTargetAttr: nil, }, + { + Description: "Targeting with alwaysIncludeDeals", + TargetData: targetData{ + priceGranularity: lookupPriceGranularity("med"), + alwaysIncludeDeals: true, + }, + Auction: auction{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + "ImpId-1": { + openrtb_ext.BidderAppnexus: {{ + Bid: bid111, + BidType: openrtb_ext.BidTypeBanner, + }}, + openrtb_ext.BidderRubicon: {{ + Bid: bid175, + BidType: openrtb_ext.BidTypeBanner, + }}, + openrtb_ext.BidderPubmatic: {{ + Bid: bid123, + BidType: openrtb_ext.BidTypeBanner, + }}, + }, + }, + }, + ExpectedPbsBids: map[string]map[openrtb_ext.BidderName][]ExpectedPbsBid{ + "ImpId-1": { + openrtb_ext.BidderAppnexus: []ExpectedPbsBid{ + { + BidTargets: map[string]string{ + "hb_bidder_appnexus": "appnexus", + "hb_pb_appnexus": "1.10", + "hb_deal_appnexus": "mydeal", + }, + }, + }, + openrtb_ext.BidderRubicon: []ExpectedPbsBid{ + { + BidTargets: map[string]string{ + "hb_bidder_rubicon": "rubicon", + "hb_pb_rubicon": "1.70", + "hb_deal_rubicon": "mydeal2", + }, + }, + }, + }, + }, + TruncateTargetAttr: nil, + }, { Description: "Full basic targeting with hd_format", TargetData: targetData{ @@ -418,7 +471,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{ includeFormat: true, }, Auction: auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "ImpId-1": { openrtb_ext.BidderAppnexus: {{ Bid: bid123, @@ -467,7 +520,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{ cachePath: "cache", }, Auction: auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "ImpId-1": { openrtb_ext.BidderAppnexus: {{ Bid: bid123, @@ -520,7 +573,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{ includeBidderKeys: true, }, Auction: auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "ImpId-1": { openrtb_ext.BidderAppnexus: {{ Bid: bid123, @@ -550,7 +603,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{ includeBidderKeys: true, }, Auction: auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "ImpId-1": { openrtb_ext.BidderAppnexus: {{ Bid: bid123, @@ -592,7 +645,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{ includeBidderKeys: true, }, Auction: auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "ImpId-1": { openrtb_ext.BidderAppnexus: {{ Bid: bid123, @@ -634,7 +687,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{ includeBidderKeys: true, }, Auction: auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "ImpId-1": { openrtb_ext.BidderAppnexus: {{ Bid: bid123, @@ -676,7 +729,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{ includeWinners: true, }, Auction: auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "ImpId-1": { openrtb_ext.BidderAppnexus: {{ Bid: bid123, @@ -711,7 +764,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{ includeWinners: true, }, Auction: auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "ImpId-1": { openrtb_ext.BidderAppnexus: {{ Bid: bid123, @@ -746,7 +799,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{ includeWinners: true, }, Auction: auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "ImpId-1": { openrtb_ext.BidderAppnexus: {{ Bid: bid123, @@ -783,7 +836,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{ includeFormat: true, }, Auction: auction{ - winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ + allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ "ImpId-1": { openrtb_ext.BidderAppnexus: { { @@ -918,7 +971,7 @@ func TestSetTargeting(t *testing.T) { auc.setRoundedPrices(test.TargetData) winningBids := make(map[string]*entities.PbsOrtbBid) // Set winning bids from the auction data - for imp, bidsByBidder := range auc.winningBidsByBidder { + for imp, bidsByBidder := range auc.allBidsByBidder { for _, bids := range bidsByBidder { for _, bid := range bids { if winningBid, ok := winningBids[imp]; ok { @@ -939,12 +992,12 @@ func TestSetTargeting(t *testing.T) { for i, expected := range expectedTargets { assert.Equal(t, expected.BidTargets, - auc.winningBidsByBidder[imp][bidder][i].BidTargets, + auc.allBidsByBidder[imp][bidder][i].BidTargets, "Test: %s\nTargeting failed for bidder %s on imp %s.", test.Description, string(bidder), imp) - assert.Equal(t, expected.TargetBidderCode, auc.winningBidsByBidder[imp][bidder][i].TargetBidderCode) + assert.Equal(t, expected.TargetBidderCode, auc.allBidsByBidder[imp][bidder][i].TargetBidderCode) } } } diff --git a/exchange/tmax_adjustments.go b/exchange/tmax_adjustments.go index 29e732995af..0d0fa31fdc6 100644 --- a/exchange/tmax_adjustments.go +++ b/exchange/tmax_adjustments.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v3/config" ) type TmaxAdjustmentsPreprocessed struct { diff --git a/exchange/tmax_adjustments_test.go b/exchange/tmax_adjustments_test.go index 7e6a02ab81e..79252339877 100644 --- a/exchange/tmax_adjustments_test.go +++ b/exchange/tmax_adjustments_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v3/config" "github.com/stretchr/testify/assert" ) diff --git a/exchange/utils.go b/exchange/utils.go index c7acfaefce3..71f7c20f8c7 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -1,7 +1,6 @@ package exchange import ( - "bytes" "context" "encoding/json" "errors" @@ -9,27 +8,29 @@ import ( "math/rand" "strings" - "github.com/buger/jsonparser" "github.com/prebid/go-gdpr/vendorconsent" gpplib "github.com/prebid/go-gpp" gppConstants "github.com/prebid/go-gpp/constants" - "github.com/prebid/openrtb/v19/openrtb2" - - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/firstpartydata" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/privacy/lmt" - "github.com/prebid/prebid-server/schain" - "github.com/prebid/prebid-server/stored_responses" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/openrtb/v20/openrtb2" + + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/firstpartydata" + "github.com/prebid/prebid-server/v3/gdpr" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/ortb" + "github.com/prebid/prebid-server/v3/privacy" + "github.com/prebid/prebid-server/v3/privacy/ccpa" + "github.com/prebid/prebid-server/v3/privacy/lmt" + "github.com/prebid/prebid-server/v3/schain" + "github.com/prebid/prebid-server/v3/stored_responses" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" ) +var errInvalidRequestExt = errors.New("request.ext is invalid") + var channelTypeMap = map[metrics.RequestType]config.ChannelType{ metrics.ReqTypeAMP: config.ChannelAMP, metrics.ReqTypeORTB2App: config.ChannelApp, @@ -47,6 +48,7 @@ type requestSplitter struct { gdprPermsBuilder gdpr.PermissionsBuilder hostSChainNode *openrtb2.SupplyChainNode bidderInfo config.BidderInfos + requestValidator ortb.RequestValidator } // cleanOpenRTBRequests splits the input request into requests which are sanitized for each bidder. Intended behavior is: @@ -57,61 +59,68 @@ type requestSplitter struct { func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, auctionReq AuctionRequest, requestExt *openrtb_ext.ExtRequest, - gdprDefaultValue gdpr.Signal, bidAdjustmentFactors map[string]float64, -) (allowedBidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { + gdprSignal gdpr.Signal, + gdprEnforced bool, + bidAdjustmentFactors map[string]float64, +) (bidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { req := auctionReq.BidRequestWrapper - aliases, errs := parseAliases(req.BidRequest) - if len(errs) > 0 { + if err := PreloadExts(req); err != nil { return } - allowedBidderRequests = make([]BidderRequest, 0) + requestAliases, requestAliasesGVLIDs, errs := getRequestAliases(req) + if len(errs) > 0 { + return + } bidderImpWithBidResp := stored_responses.InitStoredBidResponses(req.BidRequest, auctionReq.StoredBidResponses) + hasStoredAuctionResponses := len(auctionReq.StoredAuctionResponses) > 0 - impsByBidder, err := splitImps(req.BidRequest.Imp) + impsByBidder, err := splitImps(req.BidRequest.Imp, rs.requestValidator, requestAliases, hasStoredAuctionResponses, auctionReq.StoredBidResponses) if err != nil { errs = []error{err} return } - aliasesGVLIDs, errs := parseAliasesGVLIDs(req.BidRequest) - if len(errs) > 0 { + explicitBuyerUIDs, err := extractAndCleanBuyerUIDs(req) + if err != nil { + errs = []error{err} return } - var allBidderRequests []BidderRequest - allBidderRequests, errs = getAuctionBidderRequests(auctionReq, requestExt, rs.bidderToSyncerKey, impsByBidder, aliases, rs.hostSChainNode) - - bidderNameToBidderReq := buildBidResponseRequest(req.BidRequest, bidderImpWithBidResp, aliases, auctionReq.BidderImpReplaceImpID) - //this function should be executed after getAuctionBidderRequests - allBidderRequests = mergeBidderRequests(allBidderRequests, bidderNameToBidderReq) - - var gpp gpplib.GppContainer - if req.BidRequest.Regs != nil && len(req.BidRequest.Regs.GPP) > 0 { - gpp, err = gpplib.Parse(req.BidRequest.Regs.GPP) - if err != nil { - errs = append(errs, err) - } + lowerCaseExplicitBuyerUIDs := make(map[string]string) + for bidder, uid := range explicitBuyerUIDs { + lowerKey := strings.ToLower(bidder) + lowerCaseExplicitBuyerUIDs[lowerKey] = uid } - if auctionReq.Account.PriceFloors.IsAdjustForBidAdjustmentEnabled() { - //Apply BidAdjustmentFactor to imp.BidFloor - applyBidAdjustmentToFloor(allBidderRequests, bidAdjustmentFactors) + bidderParamsInReqExt, err := ExtractReqExtBidderParamsMap(req.BidRequest) + if err != nil { + errs = []error{err} + return } - gdprSignal, err := getGDPR(req) + sChainWriter, err := schain.NewSChainWriter(requestExt, rs.hostSChainNode) if err != nil { - errs = append(errs, err) + errs = []error{err} + return + } + + var gpp gpplib.GppContainer + if req.BidRequest.Regs != nil && len(req.BidRequest.Regs.GPP) > 0 { + var gppErrs []error + gpp, gppErrs = gpplib.Parse(req.BidRequest.Regs.GPP) + if len(gppErrs) > 0 { + errs = append(errs, gppErrs[0]) + } } consent, err := getConsent(req, gpp) if err != nil { errs = append(errs, err) } - gdprApplies := gdprSignal == gdpr.SignalYes || (gdprSignal == gdpr.SignalAmbiguous && gdprDefaultValue == gdpr.SignalYes) - ccpaEnforcer, err := extractCCPA(req.BidRequest, rs.privacyConfig, &auctionReq.Account, aliases, channelTypeMap[auctionReq.LegacyLabels.RType], gpp) + ccpaEnforcer, err := extractCCPA(req.BidRequest, rs.privacyConfig, &auctionReq.Account, requestAliases, channelTypeMap[auctionReq.LegacyLabels.RType], gpp) if err != nil { errs = append(errs, err) } @@ -127,13 +136,8 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, privacyLabels.COPPAEnforced = coppa privacyLabels.LMTEnforced = lmt - var gdprEnforced bool var gdprPerms gdpr.Permissions = &gdpr.AlwaysAllow{} - if gdprApplies { - gdprEnforced = auctionReq.TCF2Config.ChannelEnabled(channelTypeMap[auctionReq.LegacyLabels.RType]) - } - if gdprEnforced { privacyLabels.GDPREnforced = true parsedConsent, err := vendorconsent.ParseString(consent) @@ -143,7 +147,7 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, } gdprRequestInfo := gdpr.RequestInfo{ - AliasGVLIDs: aliasesGVLIDs, + AliasGVLIDs: requestAliasesGVLIDs, Consent: consent, GDPRSignal: gdprSignal, PublisherID: auctionReq.LegacyLabels.PubID, @@ -151,85 +155,270 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, gdprPerms = rs.gdprPermsBuilder(auctionReq.TCF2Config, gdprRequestInfo) } - // bidder level privacy policies - for _, bidderRequest := range allBidderRequests { - privacyEnforcement := privacy.Enforcement{ - COPPA: coppa, - LMT: lmt, + bidderRequests = make([]BidderRequest, 0, len(impsByBidder)) + + for bidder, imps := range impsByBidder { + fpdUserEIDsPresent := fpdUserEIDExists(req, auctionReq.FirstPartyData, bidder) + reqWrapperCopy := req.CloneAndClearImpWrappers() + bidRequestCopy := *req.BidRequest + reqWrapperCopy.BidRequest = &bidRequestCopy + reqWrapperCopy.Imp = imps + + coreBidder, isRequestAlias := resolveBidder(bidder, requestAliases) + + // apply bidder-specific schains + sChainWriter.Write(reqWrapperCopy, bidder) + + // eid scrubbing + if err := removeUnpermissionedEids(reqWrapperCopy, bidder); err != nil { + errs = append(errs, fmt.Errorf("unable to enforce request.ext.prebid.data.eidpermissions because %v", err)) + continue } - // fetchBids activity - scopedName := privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidderRequest.BidderName.String()} - fetchBidsActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityFetchBids, scopedName, privacy.NewRequestFromBidRequest(*req)) - if !fetchBidsActivityAllowed { - // skip the call to a bidder if fetchBids activity is not allowed - // do not add this bidder to allowedBidderRequests + // generate bidder-specific request ext + err = buildRequestExtForBidder(bidder, reqWrapperCopy, bidderParamsInReqExt, auctionReq.Account.AlternateBidderCodes) + if err != nil { + errs = append(errs, err) continue } - var auctionPermissions gdpr.AuctionPermissions - var gdprErr error + // apply bid adjustments + if auctionReq.Account.PriceFloors.IsAdjustForBidAdjustmentEnabled() { + applyBidAdjustmentToFloor(reqWrapperCopy, bidder, bidAdjustmentFactors) + } + + // prepare user + syncerKey := rs.bidderToSyncerKey[string(coreBidder)] + hadSync := prepareUser(reqWrapperCopy, bidder, syncerKey, lowerCaseExplicitBuyerUIDs, auctionReq.UserSyncs) + + auctionPermissions := gdprPerms.AuctionActivitiesAllowed(ctx, coreBidder, openrtb_ext.BidderName(bidder)) + + // privacy blocking + if rs.isBidderBlockedByPrivacy(reqWrapperCopy, auctionReq.Activities, auctionPermissions, coreBidder, openrtb_ext.BidderName(bidder)) { + continue + } + + // fpd + applyFPD(auctionReq.FirstPartyData, coreBidder, openrtb_ext.BidderName(bidder), isRequestAlias, reqWrapperCopy, fpdUserEIDsPresent) + + // privacy scrubbing + if err := rs.applyPrivacy(reqWrapperCopy, coreBidder, bidder, auctionReq, auctionPermissions, ccpaEnforcer, lmt, coppa); err != nil { + errs = append(errs, err) + continue + } + + // GPP downgrade: always downgrade unless we can confirm GPP is supported + if shouldSetLegacyPrivacy(rs.bidderInfo, string(coreBidder)) { + setLegacyGDPRFromGPP(reqWrapperCopy, gpp) + setLegacyUSPFromGPP(reqWrapperCopy, gpp) + } - if gdprEnforced { - auctionPermissions, gdprErr = gdprPerms.AuctionActivitiesAllowed(ctx, bidderRequest.BidderCoreName, bidderRequest.BidderName) - if !auctionPermissions.AllowBidRequest { - // auction request is not permitted by GDPR - // do not add this bidder to allowedBidderRequests - rs.me.RecordAdapterGDPRRequestBlocked(bidderRequest.BidderCoreName) + // remove imps with stored responses so they aren't sent to the bidder + if impResponses, ok := bidderImpWithBidResp[openrtb_ext.BidderName(bidder)]; ok { + removeImpsWithStoredResponses(reqWrapperCopy, impResponses) + } + + // down convert + info, ok := rs.bidderInfo[bidder] + if !ok || info.OpenRTB == nil || info.OpenRTB.Version != "2.6" { + reqWrapperCopy.Regs = ortb.CloneRegs(reqWrapperCopy.Regs) + if err := openrtb_ext.ConvertDownTo25(reqWrapperCopy); err != nil { + errs = append(errs, err) continue } } - passIDActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitUserFPD, scopedName, privacy.NewRequestFromBidRequest(*req)) - if !passIDActivityAllowed { - privacyEnforcement.UFPD = true - } else { - // run existing policies (GDPR, CCPA, COPPA, LMT) - // potentially block passing IDs based on GDPR - if gdprEnforced { - if gdprErr == nil { - privacyEnforcement.GDPRID = !auctionPermissions.PassID - } else { - privacyEnforcement.GDPRID = true - } - } - // potentially block passing IDs based on CCPA - privacyEnforcement.CCPA = ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) + // sync wrapper + if err := reqWrapperCopy.RebuildRequest(); err != nil { + errs = append(errs, err) + continue } - passGeoActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitPreciseGeo, scopedName, privacy.NewRequestFromBidRequest(*req)) - if !passGeoActivityAllowed { - privacyEnforcement.PreciseGeo = true + // choose labels + bidderLabels := metrics.AdapterLabels{ + Adapter: coreBidder, + } + if !hadSync && req.BidRequest.App == nil { + bidderLabels.CookieFlag = metrics.CookieFlagNo } else { - // run existing policies (GDPR, CCPA, COPPA, LMT) - // potentially block passing geo based on GDPR - if gdprEnforced { - if gdprErr == nil { - privacyEnforcement.GDPRGeo = !auctionPermissions.PassGeo - } else { - privacyEnforcement.GDPRGeo = true - } - } - // potentially block passing geo based on CCPA - privacyEnforcement.CCPA = ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) + bidderLabels.CookieFlag = metrics.CookieFlagYes + } + if len(reqWrapperCopy.Imp) > 0 { + bidderLabels.Source = auctionReq.LegacyLabels.Source + bidderLabels.RType = auctionReq.LegacyLabels.RType + bidderLabels.PubID = auctionReq.LegacyLabels.PubID + bidderLabels.CookieFlag = auctionReq.LegacyLabels.CookieFlag + bidderLabels.AdapterBids = metrics.AdapterBidPresent + } + bidderRequest := BidderRequest{ + BidderName: openrtb_ext.BidderName(bidder), + BidderCoreName: coreBidder, + BidRequest: reqWrapperCopy.BidRequest, + IsRequestAlias: isRequestAlias, + BidderStoredResponses: bidderImpWithBidResp[openrtb_ext.BidderName(bidder)], + ImpReplaceImpId: auctionReq.BidderImpReplaceImpID[bidder], + BidderLabels: bidderLabels, } + bidderRequests = append(bidderRequests, bidderRequest) + } - applyFPD(auctionReq.FirstPartyData, bidderRequest) + return +} + +// fpdUserEIDExists determines if req fpd config had User.EIDs +func fpdUserEIDExists(req *openrtb_ext.RequestWrapper, fpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData, bidder string) bool { + fpdToApply, exists := fpd[openrtb_ext.BidderName(bidder)] + if !exists || fpdToApply == nil { + return false + } + if fpdToApply.User == nil { + return false + } + fpdUserEIDs := fpdToApply.User.EIDs - privacyEnforcement.TID = !auctionReq.Activities.Allow(privacy.ActivityTransmitTIDs, scopedName, privacy.NewRequestFromBidRequest(*req)) + if len(fpdUserEIDs) == 0 { + return false + } + if req.User == nil { + return true + } - privacyEnforcement.Apply(bidderRequest.BidRequest, auctionReq.Account.Privacy) - allowedBidderRequests = append(allowedBidderRequests, bidderRequest) + reqUserEIDs := req.User.EIDs - // GPP downgrade: always downgrade unless we can confirm GPP is supported - if shouldSetLegacyPrivacy(rs.bidderInfo, string(bidderRequest.BidderCoreName)) { - setLegacyGDPRFromGPP(bidderRequest.BidRequest, gpp) - setLegacyUSPFromGPP(bidderRequest.BidRequest, gpp) + if len(reqUserEIDs) != len(fpdUserEIDs) { + return true + } + + // if bidder fpd didn't have user.eids then user.eids will remain the same + // hence we can use the same index to compare elements + for i := range reqUserEIDs { + pReqUserEID := &reqUserEIDs[i] + pFpdUserEID := &fpdUserEIDs[i] + if pReqUserEID != pFpdUserEID { + return true } } + return false +} - return +// removeImpsWithStoredResponses deletes imps with stored bid resp +func removeImpsWithStoredResponses(req *openrtb_ext.RequestWrapper, impBidResponses map[string]json.RawMessage) { + if len(impBidResponses) == 0 { + return + } + + imps := req.Imp + req.Imp = nil //to indicate this bidder doesn't have real requests + for _, imp := range imps { + if _, ok := impBidResponses[imp.ID]; !ok { + //add real imp back to request + req.Imp = append(req.Imp, imp) + } + } +} + +// PreloadExts ensures all exts have been unmarshalled into wrapper ext objects +func PreloadExts(req *openrtb_ext.RequestWrapper) error { + if req == nil { + return nil + } + if _, err := req.GetRequestExt(); err != nil { + return err + } + if _, err := req.GetUserExt(); err != nil { + return err + } + if _, err := req.GetDeviceExt(); err != nil { + return err + } + if _, err := req.GetRegExt(); err != nil { + return err + } + if _, err := req.GetSiteExt(); err != nil { + return err + } + if _, err := req.GetDOOHExt(); err != nil { + return err + } + if _, err := req.GetSourceExt(); err != nil { + return err + } + return nil +} + +func (rs *requestSplitter) isBidderBlockedByPrivacy(r *openrtb_ext.RequestWrapper, activities privacy.ActivityControl, auctionPermissions gdpr.AuctionPermissions, coreBidder, bidderName openrtb_ext.BidderName) bool { + // activities control + scope := privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidderName.String()} + fetchBidsActivityAllowed := activities.Allow(privacy.ActivityFetchBids, scope, privacy.NewRequestFromBidRequest(*r)) + if !fetchBidsActivityAllowed { + return true + } + + // gdpr + if !auctionPermissions.AllowBidRequest { + rs.me.RecordAdapterGDPRRequestBlocked(coreBidder) + return true + } + + return false +} + +func (rs *requestSplitter) applyPrivacy(reqWrapper *openrtb_ext.RequestWrapper, coreBidderName openrtb_ext.BidderName, bidderName string, auctionReq AuctionRequest, auctionPermissions gdpr.AuctionPermissions, ccpaEnforcer privacy.PolicyEnforcer, lmt bool, coppa bool) error { + scope := privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidderName} + ipConf := privacy.IPConf{IPV6: auctionReq.Account.Privacy.IPv6Config, IPV4: auctionReq.Account.Privacy.IPv4Config} + + bidRequest := ortb.CloneBidRequestPartial(reqWrapper.BidRequest) + reqWrapper.BidRequest = bidRequest + + passIDActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitUserFPD, scope, privacy.NewRequestFromBidRequest(*reqWrapper)) + buyerUIDSet := reqWrapper.User != nil && reqWrapper.User.BuyerUID != "" + buyerUIDRemoved := false + if !passIDActivityAllowed { + privacy.ScrubUserFPD(reqWrapper) + buyerUIDRemoved = true + } else { + if !auctionPermissions.PassID { + privacy.ScrubGdprID(reqWrapper) + buyerUIDRemoved = true + } + + if ccpaEnforcer.ShouldEnforce(bidderName) { + privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false) + buyerUIDRemoved = true + } + } + if buyerUIDSet && buyerUIDRemoved { + rs.me.RecordAdapterBuyerUIDScrubbed(coreBidderName) + } + + passGeoActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitPreciseGeo, scope, privacy.NewRequestFromBidRequest(*reqWrapper)) + if !passGeoActivityAllowed { + privacy.ScrubGeoAndDeviceIP(reqWrapper, ipConf) + } else { + if !auctionPermissions.PassGeo { + privacy.ScrubGeoAndDeviceIP(reqWrapper, ipConf) + } + if ccpaEnforcer.ShouldEnforce(bidderName) { + privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false) + } + } + + if lmt || coppa { + privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", coppa) + } + + passTIDAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitTIDs, scope, privacy.NewRequestFromBidRequest(*reqWrapper)) + if !passTIDAllowed { + privacy.ScrubTID(reqWrapper) + } + + if err := reqWrapper.RebuildRequest(); err != nil { + return err + } + + // *bidRequest = *reqWrapper.BidRequest + return nil } func shouldSetLegacyPrivacy(bidderInfo config.BidderInfos, bidder string) bool { @@ -249,14 +438,14 @@ func ccpaEnabled(account *config.Account, privacyConfig config.Privacy, requestT return privacyConfig.CCPA.Enforce } -func extractCCPA(orig *openrtb2.BidRequest, privacyConfig config.Privacy, account *config.Account, aliases map[string]string, requestType config.ChannelType, gpp gpplib.GppContainer) (privacy.PolicyEnforcer, error) { +func extractCCPA(orig *openrtb2.BidRequest, privacyConfig config.Privacy, account *config.Account, requestAliases map[string]string, requestType config.ChannelType, gpp gpplib.GppContainer) (privacy.PolicyEnforcer, error) { // Quick extra wrapper until RequestWrapper makes its way into CleanRequests ccpaPolicy, err := ccpa.ReadFromRequestWrapper(&openrtb_ext.RequestWrapper{BidRequest: orig}, gpp) if err != nil { return privacy.NilPolicyEnforcer{}, err } - validBidders := GetValidBidders(aliases) + validBidders := GetValidBidders(requestAliases) ccpaParsedPolicy, err := ccpaPolicy.Parse(validBidders) if err != nil { return privacy.NilPolicyEnforcer{}, err @@ -302,170 +491,69 @@ func ExtractReqExtBidderParamsMap(bidRequest *openrtb2.BidRequest) (map[string]j return bidderParams, nil } -func getAuctionBidderRequests(auctionRequest AuctionRequest, - requestExt *openrtb_ext.ExtRequest, - bidderToSyncerKey map[string]string, - impsByBidder map[string][]openrtb2.Imp, - aliases map[string]string, - hostSChainNode *openrtb2.SupplyChainNode) ([]BidderRequest, []error) { - - bidderRequests := make([]BidderRequest, 0, len(impsByBidder)) - req := auctionRequest.BidRequestWrapper - explicitBuyerUIDs, err := extractBuyerUIDs(req.BidRequest.User) - if err != nil { - return nil, []error{err} - } - - bidderParamsInReqExt, err := ExtractReqExtBidderParamsMap(req.BidRequest) +func buildRequestExtForBidder(bidder string, req *openrtb_ext.RequestWrapper, reqExtBidderParams map[string]json.RawMessage, cfgABC *openrtb_ext.ExtAlternateBidderCodes) error { + reqExt, err := req.GetRequestExt() if err != nil { - return nil, []error{err} - } - - sChainWriter, err := schain.NewSChainWriter(requestExt, hostSChainNode) - if err != nil { - return nil, []error{err} - } - - lowerCaseExplicitBuyerUIDs := make(map[string]string) - for bidder, uid := range explicitBuyerUIDs { - lowerKey := strings.ToLower(bidder) - lowerCaseExplicitBuyerUIDs[lowerKey] = uid - } - - var errs []error - for bidder, imps := range impsByBidder { - coreBidder, isRequestAlias := resolveBidder(bidder, aliases) - - reqCopy := *req.BidRequest - reqCopy.Imp = imps - - sChainWriter.Write(&reqCopy, bidder) - - reqCopy.Ext, err = buildRequestExtForBidder(bidder, req.BidRequest.Ext, requestExt, bidderParamsInReqExt, auctionRequest.Account.AlternateBidderCodes) - if err != nil { - return nil, []error{err} - } - - if err := removeUnpermissionedEids(&reqCopy, bidder, requestExt); err != nil { - errs = append(errs, fmt.Errorf("unable to enforce request.ext.prebid.data.eidpermissions because %v", err)) - continue - } - - bidderRequest := BidderRequest{ - BidderName: openrtb_ext.BidderName(bidder), - BidderCoreName: coreBidder, - IsRequestAlias: isRequestAlias, - BidRequest: &reqCopy, - BidderLabels: metrics.AdapterLabels{ - Source: auctionRequest.LegacyLabels.Source, - RType: auctionRequest.LegacyLabels.RType, - Adapter: coreBidder, - PubID: auctionRequest.LegacyLabels.PubID, - CookieFlag: auctionRequest.LegacyLabels.CookieFlag, - AdapterBids: metrics.AdapterBidPresent, - }, - } - - syncerKey := bidderToSyncerKey[string(coreBidder)] - if hadSync := prepareUser(&reqCopy, bidder, syncerKey, lowerCaseExplicitBuyerUIDs, auctionRequest.UserSyncs); !hadSync && req.BidRequest.App == nil { - bidderRequest.BidderLabels.CookieFlag = metrics.CookieFlagNo - } else { - bidderRequest.BidderLabels.CookieFlag = metrics.CookieFlagYes - } - - bidderRequests = append(bidderRequests, bidderRequest) + return err } - return bidderRequests, errs -} + prebid := reqExt.GetPrebid() -func buildRequestExtForBidder(bidder string, requestExt json.RawMessage, requestExtParsed *openrtb_ext.ExtRequest, bidderParamsInReqExt map[string]json.RawMessage, cfgABC *openrtb_ext.ExtAlternateBidderCodes) (json.RawMessage, error) { - // Resolve alternatebiddercode for current bidder + // Resolve Alternate Bidder Codes var reqABC *openrtb_ext.ExtAlternateBidderCodes - if len(requestExt) != 0 && requestExtParsed != nil && requestExtParsed.Prebid.AlternateBidderCodes != nil { - reqABC = requestExtParsed.Prebid.AlternateBidderCodes + if prebid != nil && prebid.AlternateBidderCodes != nil { + reqABC = prebid.AlternateBidderCodes } alternateBidderCodes := buildRequestExtAlternateBidderCodes(bidder, cfgABC, reqABC) - if (len(requestExt) == 0 || requestExtParsed == nil) && alternateBidderCodes == nil { - return nil, nil - } - - // Resolve Bidder Params - var bidderParams json.RawMessage - if bidderParamsInReqExt != nil { - bidderParams = bidderParamsInReqExt[bidder] + // Build New/Filtered Prebid Ext + prebidNew := openrtb_ext.ExtRequestPrebid{ + BidderParams: reqExtBidderParams[bidder], + AlternateBidderCodes: alternateBidderCodes, } // Copy Allowed Fields // Per: https://docs.prebid.org/prebid-server/endpoints/openrtb2/pbs-endpoint-auction.html#prebid-server-ortb2-extension-summary - prebid := openrtb_ext.ExtRequestPrebid{ - BidderParams: bidderParams, - AlternateBidderCodes: alternateBidderCodes, - } - - if requestExtParsed != nil { - prebid.Channel = requestExtParsed.Prebid.Channel - prebid.CurrencyConversions = requestExtParsed.Prebid.CurrencyConversions - prebid.Debug = requestExtParsed.Prebid.Debug - prebid.Integration = requestExtParsed.Prebid.Integration - prebid.MultiBid = buildRequestExtMultiBid(bidder, requestExtParsed.Prebid.MultiBid, alternateBidderCodes) - prebid.Sdk = requestExtParsed.Prebid.Sdk - prebid.Server = requestExtParsed.Prebid.Server - } - - // Marshal New Prebid Object - prebidJson, err := jsonutil.Marshal(prebid) - if err != nil { - return nil, err - } + if prebid != nil { + prebidNew.Channel = prebid.Channel + prebidNew.CurrencyConversions = prebid.CurrencyConversions + prebidNew.Debug = prebid.Debug + prebidNew.Integration = prebid.Integration + prebidNew.MultiBid = buildRequestExtMultiBid(bidder, prebid.MultiBid, alternateBidderCodes) + prebidNew.Sdk = prebid.Sdk + prebidNew.Server = prebid.Server + prebidNew.Targeting = buildRequestExtTargeting(prebid.Targeting) + } + + reqExt.SetPrebid(&prebidNew) + return nil +} - // Parse Existing Ext - extMap := make(map[string]json.RawMessage) - if len(requestExt) != 0 { - if err := jsonutil.Unmarshal(requestExt, &extMap); err != nil { - return nil, err - } +func buildRequestExtAlternateBidderCodes(bidder string, accABC *openrtb_ext.ExtAlternateBidderCodes, reqABC *openrtb_ext.ExtAlternateBidderCodes) *openrtb_ext.ExtAlternateBidderCodes { + if altBidderCodes := copyExtAlternateBidderCodes(bidder, reqABC); altBidderCodes != nil { + return altBidderCodes } - // Update Ext With Prebid Json - if bytes.Equal(prebidJson, []byte(`{}`)) { - delete(extMap, "prebid") - } else { - extMap["prebid"] = prebidJson + if altBidderCodes := copyExtAlternateBidderCodes(bidder, accABC); altBidderCodes != nil { + return altBidderCodes } - if len(extMap) > 0 { - return jsonutil.Marshal(extMap) - } else { - return nil, nil - } + return nil } -func buildRequestExtAlternateBidderCodes(bidder string, accABC *openrtb_ext.ExtAlternateBidderCodes, reqABC *openrtb_ext.ExtAlternateBidderCodes) *openrtb_ext.ExtAlternateBidderCodes { - if reqABC != nil { +func copyExtAlternateBidderCodes(bidder string, altBidderCodes *openrtb_ext.ExtAlternateBidderCodes) *openrtb_ext.ExtAlternateBidderCodes { + if altBidderCodes != nil { alternateBidderCodes := &openrtb_ext.ExtAlternateBidderCodes{ - Enabled: reqABC.Enabled, + Enabled: altBidderCodes.Enabled, } - if bidderCodes, ok := reqABC.Bidders[bidder]; ok { - alternateBidderCodes.Bidders = map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ - bidder: bidderCodes, - } - } - return alternateBidderCodes - } - if accABC != nil { - alternateBidderCodes := &openrtb_ext.ExtAlternateBidderCodes{ - Enabled: accABC.Enabled, - } - if bidderCodes, ok := accABC.Bidders[bidder]; ok { + if bidderCodes, ok := altBidderCodes.IsBidderInAlternateBidderCodes(bidder); ok { alternateBidderCodes.Bidders = map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ bidder: bidderCodes, } } + return alternateBidderCodes } - return nil } @@ -495,6 +583,18 @@ func buildRequestExtMultiBid(adapter string, reqMultiBid []*openrtb_ext.ExtMulti return nil } +func buildRequestExtTargeting(t *openrtb_ext.ExtRequestTargeting) *openrtb_ext.ExtRequestTargeting { + if t == nil || t.IncludeBrandCategory == nil { + return nil + } + + // only include fields bidders can use to influence their response and which does + // not expose information about other bidders or restricted auction processing + return &openrtb_ext.ExtRequestTargeting{ + IncludeBrandCategory: t.IncludeBrandCategory, + } +} + func isBidderInExtAlternateBidderCodes(adapter, currentMultiBidBidder string, adapterABC *openrtb_ext.ExtAlternateBidderCodes) bool { if adapterABC != nil { if abc, ok := adapterABC.Bidders[adapter]; ok { @@ -508,39 +608,30 @@ func isBidderInExtAlternateBidderCodes(adapter, currentMultiBidBidder string, ad return false } -// extractBuyerUIDs parses the values from user.ext.prebid.buyeruids, and then deletes those values from the ext. +// extractAndCleanBuyerUIDs parses the values from user.ext.prebid.buyeruids, and then deletes those values from the ext. // This prevents a Bidder from using these values to figure out who else is involved in the Auction. -func extractBuyerUIDs(user *openrtb2.User) (map[string]string, error) { - if user == nil { - return nil, nil - } - if len(user.Ext) == 0 { +func extractAndCleanBuyerUIDs(req *openrtb_ext.RequestWrapper) (map[string]string, error) { + if req.User == nil { return nil, nil } - var userExt openrtb_ext.ExtUser - if err := jsonutil.Unmarshal(user.Ext, &userExt); err != nil { + userExt, err := req.GetUserExt() + if err != nil { return nil, err } - if userExt.Prebid == nil { + + prebid := userExt.GetPrebid() + if prebid == nil { return nil, nil } + buyerUIDs := prebid.BuyerUIDs + + prebid.BuyerUIDs = nil + userExt.SetPrebid(prebid) + // The API guarantees that user.ext.prebid.buyeruids exists and has at least one ID defined, // as long as user.ext.prebid exists. - buyerUIDs := userExt.Prebid.BuyerUIDs - userExt.Prebid = nil - - // Remarshal (instead of removing) if the ext has other known fields - if userExt.Consent != "" || len(userExt.Eids) > 0 { - if newUserExtBytes, err := jsonutil.Marshal(userExt); err != nil { - return nil, err - } else { - user.Ext = newUserExtBytes - } - } else { - user.Ext = nil - } return buyerUIDs, nil } @@ -552,7 +643,7 @@ func extractBuyerUIDs(user *openrtb2.User) (map[string]string, error) { // The "imp.ext" value of the rubicon Imp will only contain the "prebid" values, and "rubicon" value at the "bidder" key. // // The goal here is so that Bidders only get Imps and Imp.Ext values which are intended for them. -func splitImps(imps []openrtb2.Imp) (map[string][]openrtb2.Imp, error) { +func splitImps(imps []openrtb2.Imp, requestValidator ortb.RequestValidator, requestAliases map[string]string, hasStoredAuctionResponses bool, storedBidResponses stored_responses.ImpBidderStoredResp) (map[string][]openrtb2.Imp, error) { bidderImps := make(map[string][]openrtb2.Imp) for i, imp := range imps { @@ -573,6 +664,11 @@ func splitImps(imps []openrtb2.Imp) (map[string][]openrtb2.Imp, error) { jsonutil.Unmarshal(impExtPrebidBidderJSON, &impExtPrebidBidder) } + var impExtPrebidImp map[string]json.RawMessage + if impExtPrebidImpJSON, exists := impExtPrebid["imp"]; exists { + jsonutil.Unmarshal(impExtPrebidImpJSON, &impExtPrebidImp) + } + sanitizedImpExt, err := createSanitizedImpExt(impExt, impExtPrebid) if err != nil { return nil, fmt.Errorf("unable to remove other bidder fields for imp[%d]: %v", i, err) @@ -581,6 +677,22 @@ func splitImps(imps []openrtb2.Imp) (map[string][]openrtb2.Imp, error) { for bidder, bidderExt := range impExtPrebidBidder { impCopy := imp + if impBidderFPD, exists := impExtPrebidImp[bidder]; exists { + if err := mergeImpFPD(&impCopy, impBidderFPD, i); err != nil { + return nil, err + } + impWrapper := openrtb_ext.ImpWrapper{Imp: &impCopy} + cfg := ortb.ValidationConfig{ + SkipBidderParams: true, + SkipNative: true, + } + if err := requestValidator.ValidateImp(&impWrapper, cfg, i, requestAliases, hasStoredAuctionResponses, storedBidResponses); err != nil { + return nil, &errortypes.InvalidImpFirstPartyData{ + Message: fmt.Sprintf("merging bidder imp first party data for imp %s results in an invalid imp: %v", imp.ID, err), + } + } + } + sanitizedImpExt[openrtb_ext.PrebidExtBidderKey] = bidderExt impExtJSON, err := jsonutil.Marshal(sanitizedImpExt) @@ -596,13 +708,14 @@ func splitImps(imps []openrtb2.Imp) (map[string][]openrtb2.Imp, error) { return bidderImps, nil } -var allowedImpExtFields = map[string]interface{}{ - openrtb_ext.AuctionEnvironmentKey: struct{}{}, - openrtb_ext.FirstPartyDataExtKey: struct{}{}, - openrtb_ext.FirstPartyDataContextExtKey: struct{}{}, - openrtb_ext.GPIDKey: struct{}{}, - openrtb_ext.SKAdNExtKey: struct{}{}, - openrtb_ext.TIDKey: struct{}{}, +func mergeImpFPD(imp *openrtb2.Imp, fpd json.RawMessage, index int) error { + if err := jsonutil.MergeClone(imp, fpd); err != nil { + if strings.Contains(err.Error(), "invalid json on existing object") { + return fmt.Errorf("invalid imp ext for imp[%d]", index) + } + return fmt.Errorf("invalid first party data for imp[%d]", index) + } + return nil } var allowedImpExtPrebidFields = map[string]interface{}{ @@ -610,6 +723,10 @@ var allowedImpExtPrebidFields = map[string]interface{}{ openrtb_ext.OptionsKey: struct{}{}, } +var deniedImpExtFields = map[string]interface{}{ + openrtb_ext.PrebidExtKey: struct{}{}, +} + func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map[string]json.RawMessage, error) { sanitizedImpExt := make(map[string]json.RawMessage, 6) sanitizedImpPrebidExt := make(map[string]json.RawMessage, 2) @@ -631,8 +748,8 @@ func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map } // copy reserved imp[].ext fields known to not be bidder names - for k := range allowedImpExtFields { - if v, exists := impExt[k]; exists { + for k, v := range impExt { + if _, exists := deniedImpExtFields[k]; !exists { sanitizedImpExt[k] = v } } @@ -641,11 +758,9 @@ func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map } // prepareUser changes req.User so that it's ready for the given bidder. -// This *will* mutate the request, but will *not* mutate any objects nested inside it. -// // In this function, "givenBidder" may or may not be an alias. "coreBidder" must *not* be an alias. // It returns true if a Cookie User Sync existed, and false otherwise. -func prepareUser(req *openrtb2.BidRequest, givenBidder, syncerKey string, explicitBuyerUIDs map[string]string, usersyncs IdFetcher) bool { +func prepareUser(req *openrtb_ext.RequestWrapper, givenBidder, syncerKey string, explicitBuyerUIDs map[string]string, usersyncs IdFetcher) bool { cookieId, hadCookie, _ := usersyncs.GetUID(syncerKey) if id, ok := explicitBuyerUIDs[strings.ToLower(givenBidder)]; ok { @@ -673,42 +788,32 @@ func copyWithBuyerUID(user *openrtb2.User, buyerUID string) *openrtb2.User { return user } -// removeUnpermissionedEids modifies the request to remove any request.user.ext.eids not permissions for the specific bidder -func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, requestExt *openrtb_ext.ExtRequest) error { +// removeUnpermissionedEids modifies the request to remove any request.user.eids not permissions for the specific bidder +func removeUnpermissionedEids(reqWrapper *openrtb_ext.RequestWrapper, bidder string) error { // ensure request might have eids (as much as we can check before unmarshalling) - if request.User == nil || len(request.User.Ext) == 0 { + if reqWrapper.User == nil || len(reqWrapper.User.EIDs) == 0 { return nil } // ensure request has eid permissions to enforce - if requestExt == nil || requestExt.Prebid.Data == nil || len(requestExt.Prebid.Data.EidPermissions) == 0 { - return nil - } - - // low level unmarshal to preserve other request.user.ext values. prebid server is non-destructive. - var userExt map[string]json.RawMessage - if err := jsonutil.Unmarshal(request.User.Ext, &userExt); err != nil { + reqExt, err := reqWrapper.GetRequestExt() + if err != nil { return err } - - eidsJSON, eidsSpecified := userExt["eids"] - if !eidsSpecified { + if reqExt == nil { return nil } - var eids []openrtb2.EID - if err := jsonutil.Unmarshal(eidsJSON, &eids); err != nil { - return err - } - - // exit early if there are no eids (empty array) - if len(eids) == 0 { + reqExtPrebid := reqExt.GetPrebid() + if reqExtPrebid == nil || reqExtPrebid.Data == nil || len(reqExtPrebid.Data.EidPermissions) == 0 { return nil } + eids := reqWrapper.User.EIDs + // translate eid permissions to a map for quick lookup eidRules := make(map[string][]string) - for _, p := range requestExt.Prebid.Data.EidPermissions { + for _, p := range reqExtPrebid.Data.EidPermissions { eidRules[p.Source] = p.Bidders } @@ -736,37 +841,14 @@ func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, reque return nil } - // marshal eidsAllowed back to userExt if len(eidsAllowed) == 0 { - delete(userExt, "eids") + reqWrapper.User.EIDs = nil } else { - eidsRaw, err := jsonutil.Marshal(eidsAllowed) - if err != nil { - return err - } - userExt["eids"] = eidsRaw + reqWrapper.User.EIDs = eidsAllowed } - - // exit early if userExt is empty - if len(userExt) == 0 { - setUserExtWithCopy(request, nil) - return nil - } - - userExtJSON, err := jsonutil.Marshal(userExt) - if err != nil { - return err - } - setUserExtWithCopy(request, userExtJSON) return nil } -func setUserExtWithCopy(request *openrtb2.BidRequest, userExtJSON json.RawMessage) { - userCopy := *request.User - userCopy.Ext = userExtJSON - request.User = &userCopy -} - // resolveBidder returns the known BidderName associated with bidder, if bidder is an alias. If it's not an alias, the bidder is returned. func resolveBidder(bidder string, requestAliases map[string]string) (openrtb_ext.BidderName, bool) { normalisedBidderName, _ := openrtb_ext.NormalizeBidderName(bidder) @@ -778,36 +860,23 @@ func resolveBidder(bidder string, requestAliases map[string]string) (openrtb_ext return normalisedBidderName, false } -// parseAliases parses the aliases from the BidRequest -func parseAliases(orig *openrtb2.BidRequest) (map[string]string, []error) { - var aliases map[string]string - if value, dataType, _, err := jsonparser.Get(orig.Ext, openrtb_ext.PrebidExtKey, "aliases"); dataType == jsonparser.Object && err == nil { - if err := jsonutil.Unmarshal(value, &aliases); err != nil { - return nil, []error{err} - } - } else if dataType != jsonparser.NotExist && err != jsonparser.KeyPathNotFoundError { - return nil, []error{err} +func getRequestAliases(req *openrtb_ext.RequestWrapper) (map[string]string, map[string]uint16, []error) { + reqExt, err := req.GetRequestExt() + if err != nil { + return nil, nil, []error{errInvalidRequestExt} } - return aliases, nil -} -// parseAliasesGVLIDs parses the Bidder Alias GVLIDs from the BidRequest -func parseAliasesGVLIDs(orig *openrtb2.BidRequest) (map[string]uint16, []error) { - var aliasesGVLIDs map[string]uint16 - if value, dataType, _, err := jsonparser.Get(orig.Ext, openrtb_ext.PrebidExtKey, "aliasgvlids"); dataType == jsonparser.Object && err == nil { - if err := jsonutil.Unmarshal(value, &aliasesGVLIDs); err != nil { - return nil, []error{err} - } - } else if dataType != jsonparser.NotExist && err != jsonparser.KeyPathNotFoundError { - return nil, []error{err} + if prebid := reqExt.GetPrebid(); prebid != nil { + return prebid.Aliases, prebid.AliasGVLIDs, nil } - return aliasesGVLIDs, nil + + return nil, nil, nil } -func GetValidBidders(aliases map[string]string) map[string]struct{} { +func GetValidBidders(requestAliases map[string]string) map[string]struct{} { validBidders := openrtb_ext.BuildBidderNameHashSet() - for k := range aliases { + for k := range requestAliases { validBidders[k] = struct{}{} } @@ -859,14 +928,15 @@ func getExtCacheInstructions(requestExtPrebid *openrtb_ext.ExtRequestPrebid) ext func getExtTargetData(requestExtPrebid *openrtb_ext.ExtRequestPrebid, cacheInstructions extCacheInstructions) *targetData { if requestExtPrebid != nil && requestExtPrebid.Targeting != nil { return &targetData{ - includeWinners: *requestExtPrebid.Targeting.IncludeWinners, - includeBidderKeys: *requestExtPrebid.Targeting.IncludeBidderKeys, + alwaysIncludeDeals: requestExtPrebid.Targeting.AlwaysIncludeDeals, + includeBidderKeys: ptrutil.ValueOrDefault(requestExtPrebid.Targeting.IncludeBidderKeys), includeCacheBids: cacheInstructions.cacheBids, includeCacheVast: cacheInstructions.cacheVAST, includeFormat: requestExtPrebid.Targeting.IncludeFormat, - priceGranularity: *requestExtPrebid.Targeting.PriceGranularity, - mediaTypePriceGranularity: requestExtPrebid.Targeting.MediaTypePriceGranularity, + includeWinners: ptrutil.ValueOrDefault(requestExtPrebid.Targeting.IncludeWinners), + mediaTypePriceGranularity: ptrutil.ValueOrDefault(requestExtPrebid.Targeting.MediaTypePriceGranularity), preferDeals: requestExtPrebid.Targeting.PreferDeals, + priceGranularity: ptrutil.ValueOrDefault(requestExtPrebid.Targeting.PriceGranularity), } } @@ -911,14 +981,19 @@ func getExtBidAdjustmentFactors(requestExtPrebid *openrtb_ext.ExtRequestPrebid) return nil } -func applyFPD(fpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData, r BidderRequest) { +func applyFPD(fpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData, + coreBidderName openrtb_ext.BidderName, + bidderName openrtb_ext.BidderName, + isRequestAlias bool, + reqWrapper *openrtb_ext.RequestWrapper, + fpdUserEIDsPresent bool) { if fpd == nil { return } - bidder := r.BidderCoreName - if r.IsRequestAlias { - bidder = r.BidderName + bidder := coreBidderName + if isRequestAlias { + bidder = bidderName } fpdToApply, exists := fpd[bidder] @@ -927,77 +1002,31 @@ func applyFPD(fpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyD } if fpdToApply.Site != nil { - r.BidRequest.Site = fpdToApply.Site + reqWrapper.Site = fpdToApply.Site } if fpdToApply.App != nil { - r.BidRequest.App = fpdToApply.App + reqWrapper.App = fpdToApply.App } if fpdToApply.User != nil { - //BuyerUID is a value obtained between fpd extraction and fpd application. - //BuyerUID needs to be set back to fpd before applying this fpd to final bidder request - if r.BidRequest.User != nil && len(r.BidRequest.User.BuyerUID) > 0 { - fpdToApply.User.BuyerUID = r.BidRequest.User.BuyerUID - } - r.BidRequest.User = fpdToApply.User - } -} - -func buildBidResponseRequest(req *openrtb2.BidRequest, - bidderImpResponses stored_responses.BidderImpsWithBidResponses, - aliases map[string]string, - bidderImpReplaceImpID stored_responses.BidderImpReplaceImpID) map[openrtb_ext.BidderName]BidderRequest { - - bidderToBidderResponse := make(map[openrtb_ext.BidderName]BidderRequest) - - for bidderName, impResps := range bidderImpResponses { - resolvedBidder, isRequestAlias := resolveBidder(string(bidderName), aliases) - bidderToBidderResponse[bidderName] = BidderRequest{ - BidRequest: req, - BidderCoreName: resolvedBidder, - BidderName: bidderName, - BidderStoredResponses: impResps, - ImpReplaceImpId: bidderImpReplaceImpID[string(resolvedBidder)], - IsRequestAlias: isRequestAlias, - BidderLabels: metrics.AdapterLabels{Adapter: resolvedBidder}, - } - } - return bidderToBidderResponse -} - -func mergeBidderRequests(allBidderRequests []BidderRequest, bidderNameToBidderReq map[openrtb_ext.BidderName]BidderRequest) []BidderRequest { - if len(allBidderRequests) == 0 && len(bidderNameToBidderReq) == 0 { - return allBidderRequests - } - if len(allBidderRequests) == 0 && len(bidderNameToBidderReq) > 0 { - for _, v := range bidderNameToBidderReq { - allBidderRequests = append(allBidderRequests, v) - } - return allBidderRequests - } else if len(allBidderRequests) > 0 && len(bidderNameToBidderReq) > 0 { - //merge bidder requests with real imps and imps with stored resp - for bn, br := range bidderNameToBidderReq { - found := false - for i, ar := range allBidderRequests { - if ar.BidderName == bn { - //bidder req with real imps and imps with stored resp - allBidderRequests[i].BidderStoredResponses = br.BidderStoredResponses - found = true - break - } + if reqWrapper.User != nil { + if len(reqWrapper.User.BuyerUID) > 0 { + //BuyerUID is a value obtained between fpd extraction and fpd application. + //BuyerUID needs to be set back to fpd before applying this fpd to final bidder request + fpdToApply.User.BuyerUID = reqWrapper.User.BuyerUID } - if !found { - //bidder req with stored bid responses only - br.BidRequest.Imp = nil // to indicate this bidder request has bidder responses only - allBidderRequests = append(allBidderRequests, br) + + // if FPD config didn't have user.eids - use reqWrapper.User.EIDs after removeUnpermissionedEids + if !fpdUserEIDsPresent { + fpdToApply.User.EIDs = reqWrapper.User.EIDs } } + reqWrapper.User = fpdToApply.User } - return allBidderRequests } -func setLegacyGDPRFromGPP(r *openrtb2.BidRequest, gpp gpplib.GppContainer) { +func setLegacyGDPRFromGPP(r *openrtb_ext.RequestWrapper, gpp gpplib.GppContainer) { if r.Regs != nil && r.Regs.GDPR == nil { if r.Regs.GPPSID != nil { // Set to 0 unless SID exists @@ -1026,13 +1055,12 @@ func setLegacyGDPRFromGPP(r *openrtb2.BidRequest, gpp gpplib.GppContainer) { } } } - } -func setLegacyUSPFromGPP(r *openrtb2.BidRequest, gpp gpplib.GppContainer) { + +func setLegacyUSPFromGPP(r *openrtb_ext.RequestWrapper, gpp gpplib.GppContainer) { if r.Regs == nil { return } - if len(r.Regs.USPrivacy) > 0 || r.Regs.GPPSID == nil { return } @@ -1047,7 +1075,6 @@ func setLegacyUSPFromGPP(r *openrtb2.BidRequest, gpp gpplib.GppContainer) { } } } - } func WrapJSONInData(data []byte) []byte { @@ -1108,24 +1135,20 @@ func getPrebidMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { } } -func applyBidAdjustmentToFloor(allBidderRequests []BidderRequest, bidAdjustmentFactors map[string]float64) { - - if len(bidAdjustmentFactors) == 0 { +func applyBidAdjustmentToFloor(req *openrtb_ext.RequestWrapper, bidder string, adjustmentFactors map[string]float64) { + if len(adjustmentFactors) == 0 { return } - for _, bidderRequest := range allBidderRequests { - bidAdjustment := 1.0 - - if bidAdjustemntValue, ok := bidAdjustmentFactors[string(bidderRequest.BidderName)]; ok { - bidAdjustment = bidAdjustemntValue - } + bidAdjustment := 1.0 + if v, ok := adjustmentFactors[bidder]; ok && v != 0.0 { + bidAdjustment = v + } - if bidAdjustment != 1.0 { - for index, imp := range bidderRequest.BidRequest.Imp { - imp.BidFloor = imp.BidFloor / bidAdjustment - bidderRequest.BidRequest.Imp[index] = imp - } + if bidAdjustment != 1.0 { + for index, imp := range req.Imp { + imp.BidFloor = imp.BidFloor / bidAdjustment + req.Imp[index] = imp } } } diff --git a/exchange/utils_test.go b/exchange/utils_test.go index b413a6b292e..64f0973ecf4 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -4,26 +4,28 @@ import ( "context" "encoding/json" "errors" - "fmt" "sort" "testing" gpplib "github.com/prebid/go-gpp" "github.com/prebid/go-gpp/constants" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/firstpartydata" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/firstpartydata" + "github.com/prebid/prebid-server/v3/gdpr" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/privacy" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" ) +const deviceUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36" + // permissionsMock mocks the Permissions interface for tests type permissionsMock struct { allowAllBidders bool @@ -41,7 +43,7 @@ func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return true, nil } -func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (gdpr.AuctionPermissions, error) { +func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) gdpr.AuctionPermissions { permissions := gdpr.AuctionPermissions{ PassGeo: p.passGeo, PassID: p.passID, @@ -49,7 +51,7 @@ func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidderCo if p.allowAllBidders { permissions.AllowBidRequest = true - return permissions, p.activitiesError + return permissions } for _, allowedBidder := range p.allowedBidders { @@ -58,7 +60,7 @@ func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidderCo } } - return permissions, p.activitiesError + return permissions } type fakePermissionsBuilder struct { @@ -88,10 +90,11 @@ func assertReq(t *testing.T, bidderRequests []BidderRequest, func TestSplitImps(t *testing.T) { testCases := []struct { - description string - givenImps []openrtb2.Imp - expectedImps map[string][]openrtb2.Imp - expectedError string + description string + givenImps []openrtb2.Imp + validatorErrors []error + expectedImps map[string][]openrtb2.Imp + expectedError string }{ { description: "Nil", @@ -204,10 +207,105 @@ func TestSplitImps(t *testing.T) { }, expectedError: "invalid json for imp[0]: do not know how to skip: 109", }, + { + description: "Malformed imp.ext.prebid.imp", + givenImps: []openrtb2.Imp{ + {ID: "imp1", Ext: json.RawMessage(`{"prebid": {"imp": malformed}}`)}, + }, + expectedError: "invalid json for imp[0]: do not know how to skip: 109", + }, + { + description: "valid FPD at imp.ext.prebid.imp for valid bidder", + givenImps: []openrtb2.Imp{ + { + ID: "imp1", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 10, + H: 20, + }, + }, + }, + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1paramA":"imp1valueA"}},"imp":{"bidderA":{"id":"impFPD", "banner":{"format":[{"w":30,"h":40}]}}}}}`), + }, + }, + expectedImps: map[string][]openrtb2.Imp{ + "bidderA": { + { + ID: "impFPD", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 30, + H: 40, + }, + }, + }, + Ext: json.RawMessage(`{"bidder":{"imp1paramA":"imp1valueA"}}`), + }, + }, + }, + expectedError: "", + }, + { + description: "valid FPD at imp.ext.prebid.imp for unknown bidder", + givenImps: []openrtb2.Imp{ + { + ID: "imp1", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 10, + H: 20, + }, + }, + }, + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderB":{"imp1paramB":"imp1valueB"}},"imp":{"bidderA":{"id":"impFPD", "banner":{"format":[{"w":30,"h":40}]}}}}}`), + }, + }, + expectedImps: map[string][]openrtb2.Imp{ + "bidderB": { + { + ID: "imp1", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 10, + H: 20, + }, + }, + }, + Ext: json.RawMessage(`{"bidder":{"imp1paramB":"imp1valueB"}}`), + }, + }, + }, + expectedError: "", + }, + { + description: "invalid FPD at imp.ext.prebid.imp for valid bidder", + givenImps: []openrtb2.Imp{ + { + ID: "imp1", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 10, + H: 20, + }, + }, + }, + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1paramA":"imp1valueA"}},"imp":{"bidderA":{"id":"impFPD", "banner":{"format":[{"w":0,"h":0}]}}}}}`), + }, + }, + validatorErrors: []error{errors.New("some error")}, + expectedImps: nil, + expectedError: "merging bidder imp first party data for imp imp1 results in an invalid imp: [some error]", + }, } for _, test := range testCases { - imps, err := splitImps(test.givenImps) + imps, err := splitImps(test.givenImps, &mockRequestValidator{errors: test.validatorErrors}, nil, false, nil) if test.expectedError == "" { assert.NoError(t, err, test.description+":err") @@ -219,6 +317,201 @@ func TestSplitImps(t *testing.T) { } } +func TestMergeImpFPD(t *testing.T) { + imp1 := &openrtb2.Imp{ + ID: "imp1", + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](400), + }, + } + + tests := []struct { + description string + imp *openrtb2.Imp + fpd json.RawMessage + wantImp *openrtb2.Imp + wantError bool + }{ + { + description: "nil", + imp: nil, + fpd: nil, + wantImp: nil, + wantError: true, + }, + { + description: "nil_fpd", + imp: imp1, + fpd: nil, + wantImp: imp1, + wantError: true, + }, + { + description: "empty_fpd", + imp: imp1, + fpd: json.RawMessage(`{}`), + wantImp: imp1, + wantError: false, + }, + { + description: "nil_imp", + imp: nil, + fpd: json.RawMessage(`{}`), + wantImp: nil, + wantError: true, + }, + { + description: "zero_value_imp", + imp: &openrtb2.Imp{}, + fpd: json.RawMessage(`{}`), + wantImp: &openrtb2.Imp{}, + wantError: false, + }, + { + description: "invalid_json_on_existing_imp", + imp: &openrtb2.Imp{ + Ext: json.RawMessage(`malformed`), + }, + fpd: json.RawMessage(`{"ext": {"a":1}}`), + wantImp: &openrtb2.Imp{ + Ext: json.RawMessage(`malformed`), + }, + wantError: true, + }, + { + description: "invalid_json_in_fpd", + imp: &openrtb2.Imp{ + Ext: json.RawMessage(`{"ext": {"a":1}}`), + }, + fpd: json.RawMessage(`malformed`), + wantImp: &openrtb2.Imp{ + Ext: json.RawMessage(`{"ext": {"a":1}}`), + }, + wantError: true, + }, + { + description: "override_everything", + imp: &openrtb2.Imp{ + ID: "id1", + Metric: []openrtb2.Metric{{Type: "type1", Value: 1, Vendor: "vendor1"}}, + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](1), + H: ptrutil.ToPtr[int64](2), + Format: []openrtb2.Format{ + { + W: 10, + H: 20, + Ext: json.RawMessage(`{"formatkey1":"formatval1"}`), + }, + }, + }, + Instl: 1, + BidFloor: 1, + Ext: json.RawMessage(`{"cool":"test"}`), + }, + fpd: json.RawMessage(`{"id": "id2", "metric": [{"type":"type2", "value":2, "vendor":"vendor2"}], "banner": {"w":100, "h": 200, "format": [{"w":1000, "h":2000, "ext":{"formatkey1":"formatval2"}}]}, "instl":2, "bidfloor":2, "ext":{"cool":"test2"} }`), + wantImp: &openrtb2.Imp{ + ID: "id2", + Metric: []openrtb2.Metric{{Type: "type2", Value: 2, Vendor: "vendor2"}}, + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](100), + H: ptrutil.ToPtr[int64](200), + Format: []openrtb2.Format{ + { + W: 1000, + H: 2000, + Ext: json.RawMessage(`{"formatkey1":"formatval2"}`), + }, + }, + }, + Instl: 2, + BidFloor: 2, + Ext: json.RawMessage(`{"cool":"test2"}`), + }, + }, + { + description: "override_partial_simple", + imp: imp1, + fpd: json.RawMessage(`{"id": "456", "banner": {"format": [{"w":1, "h":2}]} }`), + wantImp: &openrtb2.Imp{ + ID: "456", + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](200), + H: ptrutil.ToPtr[int64](400), + Format: []openrtb2.Format{ + { + W: 1, + H: 2, + }, + }, + }, + }, + }, + { + description: "override_partial_complex", + imp: &openrtb2.Imp{ + ID: "id1", + Metric: []openrtb2.Metric{{Type: "type1", Value: 1, Vendor: "vendor1"}}, + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](1), + H: ptrutil.ToPtr[int64](2), + Format: []openrtb2.Format{ + { + W: 10, + H: 20, + Ext: json.RawMessage(`{"formatkey1":"formatval1"}`), + }, + }, + }, + Instl: 1, + TagID: "tag1", + BidFloor: 1, + Rwdd: 1, + DT: 1, + IframeBuster: []string{"buster1", "buster2"}, + Ext: json.RawMessage(`{"cool1":"test1", "cool2":"test2"}`), + }, + fpd: json.RawMessage(`{"id": "id2", "metric": [{"type":"type2", "value":2, "vendor":"vendor2"}], "banner": {"w":100, "format": [{"w":1000, "h":2000, "ext":{"formatkey1":"formatval11"}}]}, "instl":2, "bidfloor":2, "ext":{"cool1":"test11"} }`), + wantImp: &openrtb2.Imp{ + ID: "id2", + Metric: []openrtb2.Metric{{Type: "type2", Value: 2, Vendor: "vendor2"}}, + Banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](100), + H: ptrutil.ToPtr[int64](2), + Format: []openrtb2.Format{ + { + W: 1000, + H: 2000, + Ext: json.RawMessage(`{"formatkey1":"formatval11"}`), + }, + }, + }, + Instl: 2, + TagID: "tag1", + BidFloor: 2, + Rwdd: 1, + DT: 1, + IframeBuster: []string{"buster1", "buster2"}, + Ext: json.RawMessage(`{"cool1":"test11","cool2":"test2"}`), + }, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + err := mergeImpFPD(test.imp, test.fpd, 1) + assert.Equal(t, test.wantImp, test.imp) + + if test.wantError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + func TestCreateSanitizedImpExt(t *testing.T) { testCases := []struct { description string @@ -314,80 +607,83 @@ func TestCreateSanitizedImpExt(t *testing.T) { { description: "imp.ext", givenImpExt: map[string]json.RawMessage{ - "anyBidder": json.RawMessage(`"anyBidderValues"`), - "data": json.RawMessage(`"anyData"`), - "context": json.RawMessage(`"anyContext"`), - "skadn": json.RawMessage(`"anySKAdNetwork"`), - "gpid": json.RawMessage(`"anyGPID"`), - "tid": json.RawMessage(`"anyTID"`), + "arbitraryField": json.RawMessage(`"arbitraryValue"`), + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), }, givenImpExtPrebid: map[string]json.RawMessage{}, expected: map[string]json.RawMessage{ - "data": json.RawMessage(`"anyData"`), - "context": json.RawMessage(`"anyContext"`), - "skadn": json.RawMessage(`"anySKAdNetwork"`), - "gpid": json.RawMessage(`"anyGPID"`), - "tid": json.RawMessage(`"anyTID"`), + "arbitraryField": json.RawMessage(`"arbitraryValue"`), + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), }, expectedError: "", }, { description: "imp.ext + imp.ext.prebid - Prebid Bidder Only", givenImpExt: map[string]json.RawMessage{ - "anyBidder": json.RawMessage(`"anyBidderValues"`), - "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), - "data": json.RawMessage(`"anyData"`), - "context": json.RawMessage(`"anyContext"`), - "skadn": json.RawMessage(`"anySKAdNetwork"`), - "gpid": json.RawMessage(`"anyGPID"`), - "tid": json.RawMessage(`"anyTID"`), + "arbitraryField": json.RawMessage(`"arbitraryValue"`), + "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), }, givenImpExtPrebid: map[string]json.RawMessage{ "bidder": json.RawMessage(`"anyBidder"`), }, expected: map[string]json.RawMessage{ - "data": json.RawMessage(`"anyData"`), - "context": json.RawMessage(`"anyContext"`), - "skadn": json.RawMessage(`"anySKAdNetwork"`), - "gpid": json.RawMessage(`"anyGPID"`), - "tid": json.RawMessage(`"anyTID"`), + "arbitraryField": json.RawMessage(`"arbitraryValue"`), + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), }, expectedError: "", }, { description: "imp.ext + imp.ext.prebid - Prebid Bidder + Other Forbidden Value", givenImpExt: map[string]json.RawMessage{ - "anyBidder": json.RawMessage(`"anyBidderValues"`), - "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), - "data": json.RawMessage(`"anyData"`), - "context": json.RawMessage(`"anyContext"`), - "skadn": json.RawMessage(`"anySKAdNetwork"`), - "gpid": json.RawMessage(`"anyGPID"`), - "tid": json.RawMessage(`"anyTID"`), + "arbitraryField": json.RawMessage(`"arbitraryValue"`), + "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), }, givenImpExtPrebid: map[string]json.RawMessage{ "bidder": json.RawMessage(`"anyBidder"`), "forbidden": json.RawMessage(`"anyValue"`), }, expected: map[string]json.RawMessage{ - "data": json.RawMessage(`"anyData"`), - "context": json.RawMessage(`"anyContext"`), - "skadn": json.RawMessage(`"anySKAdNetwork"`), - "gpid": json.RawMessage(`"anyGPID"`), - "tid": json.RawMessage(`"anyTID"`), + "arbitraryField": json.RawMessage(`"arbitraryValue"`), + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), }, expectedError: "", }, { description: "imp.ext + imp.ext.prebid - Prebid Bidder + Other Allowed Values", givenImpExt: map[string]json.RawMessage{ - "anyBidder": json.RawMessage(`"anyBidderValues"`), - "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), - "data": json.RawMessage(`"anyData"`), - "context": json.RawMessage(`"anyContext"`), - "skadn": json.RawMessage(`"anySKAdNetwork"`), - "gpid": json.RawMessage(`"anyGPID"`), - "tid": json.RawMessage(`"anyTID"`), + "arbitraryField": json.RawMessage(`"arbitraryValue"`), + "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), }, givenImpExtPrebid: map[string]json.RawMessage{ "bidder": json.RawMessage(`"anyBidder"`), @@ -395,12 +691,13 @@ func TestCreateSanitizedImpExt(t *testing.T) { "options": json.RawMessage(`"anyOptions"`), }, expected: map[string]json.RawMessage{ - "prebid": json.RawMessage(`{"is_rewarded_inventory":"anyIsRewardedInventory","options":"anyOptions"}`), - "data": json.RawMessage(`"anyData"`), - "context": json.RawMessage(`"anyContext"`), - "skadn": json.RawMessage(`"anySKAdNetwork"`), - "gpid": json.RawMessage(`"anyGPID"`), - "tid": json.RawMessage(`"anyTID"`), + "arbitraryField": json.RawMessage(`"arbitraryValue"`), + "prebid": json.RawMessage(`{"is_rewarded_inventory":"anyIsRewardedInventory","options":"anyOptions"}`), + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + "gpid": json.RawMessage(`"anyGPID"`), + "tid": json.RawMessage(`"anyTID"`), }, expectedError: "", }, @@ -438,7 +735,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { consentedVendors: map[string]bool{"appnexus": true}, }, { - req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest(t)}, UserSyncs: &emptyUsersync{}, TCF2Config: emptyTCF2Config}, + req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest()}, UserSyncs: &emptyUsersync{}, TCF2Config: emptyTCF2Config}, bidReqAssertions: assertReq, hasError: false, applyCOPPA: false, @@ -471,7 +768,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { hostSChainNode: nil, bidderInfo: config.BidderInfos{}, } - bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo, map[string]float64{}) + bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo, false, map[string]float64{}) if test.hasError { assert.NotNil(t, err, "Error shouldn't be nil") } else { @@ -505,17 +802,17 @@ func TestCleanOpenRTBRequestsWithFPD(t *testing.T) { }, { description: "Pass valid FPD data for bidders specified in request", - req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest(t)}, UserSyncs: &emptyUsersync{}, FirstPartyData: fpd, TCF2Config: emptyTCF2Config}, + req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest()}, UserSyncs: &emptyUsersync{}, FirstPartyData: fpd, TCF2Config: emptyTCF2Config}, fpdExpected: true, }, { description: "Bidders specified in request but there is no fpd data for this bidder", - req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest(t)}, UserSyncs: &emptyUsersync{}, FirstPartyData: make(map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData), TCF2Config: emptyTCF2Config}, + req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest()}, UserSyncs: &emptyUsersync{}, FirstPartyData: make(map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData), TCF2Config: emptyTCF2Config}, fpdExpected: false, }, { description: "No FPD data passed", - req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest(t)}, UserSyncs: &emptyUsersync{}, FirstPartyData: nil, TCF2Config: emptyTCF2Config}, + req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest()}, UserSyncs: &emptyUsersync{}, FirstPartyData: nil, TCF2Config: emptyTCF2Config}, fpdExpected: false, }, } @@ -537,7 +834,7 @@ func TestCleanOpenRTBRequestsWithFPD(t *testing.T) { bidderInfo: config.BidderInfos{}, } - bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo, map[string]float64{}) + bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo, false, map[string]float64{}) assert.Empty(t, err, "No errors should be returned") for _, bidderRequest := range bidderRequests { bidderName := bidderRequest.BidderName @@ -614,8 +911,8 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { { ID: "imp-id1", Video: &openrtb2.Video{ - W: 300, - H: 250, + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](250), }, Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), }, @@ -638,8 +935,8 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { { ID: "imp-id1", Video: &openrtb2.Video{ - W: 300, - H: 250, + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](250), }, Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), }, @@ -668,10 +965,10 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { { ID: "imp-id1", Video: &openrtb2.Video{ - W: 300, - H: 250, + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](250), }, - Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"},"bidderB":{"placementId":"456"}}}}`), }, }, expectedBidderRequests: map[string]BidderRequest{ @@ -699,10 +996,10 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { { ID: "imp-id1", Video: &openrtb2.Video{ - W: 300, - H: 250, + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](250), }, - Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"},"bidderB":{"placementId":"456"}}}}`), }, { ID: "imp-id2", @@ -739,10 +1036,10 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { { ID: "imp-id1", Video: &openrtb2.Video{ - W: 300, - H: 250, + W: ptrutil.ToPtr[int64](300), + H: ptrutil.ToPtr[int64](250), }, - Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"},"bidderB":{"placementId":"456"}}}}`), }, { ID: "imp-id2", @@ -808,11 +1105,11 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { imps: []openrtb2.Imp{ { ID: "imp-id1", - Ext: json.RawMessage(`"prebid": {}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), }, { ID: "imp-id2", - Ext: json.RawMessage(`"prebid": {}`), + Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), }, }, expectedBidderRequests: map[string]BidderRequest{ @@ -852,7 +1149,7 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { bidderInfo: config.BidderInfos{}, } - actualBidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, map[string]float64{}) + actualBidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, map[string]float64{}) assert.Empty(t, err, "No errors should be returned") assert.Len(t, actualBidderRequests, len(test.expectedBidderRequests), "result len doesn't match for testCase %s", test.description) for _, actualBidderRequest := range actualBidderRequests { @@ -980,10 +1277,10 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { } for _, test := range testCases { - req := newBidRequest(t) + req := newBidRequest() req.Ext = test.reqExt req.Regs = &openrtb2.Regs{ - Ext: json.RawMessage(`{"us_privacy":"` + test.ccpaConsent + `"}`), + USPrivacy: test.ccpaConsent, } privacyConfig := config.Privacy{ @@ -1011,26 +1308,31 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { }, }.Builder + metricsMock := metrics.MetricsEngineMock{} + metricsMock.Mock.On("RecordAdapterBuyerUIDScrubbed", mock.Anything).Return() + bidderToSyncerKey := map[string]string{} reqSplitter := &requestSplitter{ bidderToSyncerKey: bidderToSyncerKey, - me: &metrics.MetricsEngineMock{}, + me: &metricsMock, privacyConfig: privacyConfig, gdprPermsBuilder: gdprPermissionsBuilder, hostSChainNode: nil, bidderInfo: config.BidderInfos{}, } - bidderRequests, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, map[string]float64{}) + bidderRequests, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, map[string]float64{}) result := bidderRequests[0] assert.Nil(t, errs) if test.expectDataScrub { assert.Equal(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID") assert.Equal(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5") + metricsMock.AssertCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus) } else { assert.NotEqual(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID") assert.NotEqual(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5") + metricsMock.AssertNotCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus) } assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels") } @@ -1038,32 +1340,32 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { testCases := []struct { - description string - reqExt json.RawMessage - reqRegsExt json.RawMessage - expectError error + description string + reqExt json.RawMessage + reqRegsPrivacy string + expectError error }{ { - description: "Invalid Consent", - reqExt: json.RawMessage(`{"prebid":{"nosale":["*"]}}`), - reqRegsExt: json.RawMessage(`{"us_privacy":"malformed"}`), + description: "Invalid Consent", + reqExt: json.RawMessage(`{"prebid":{"nosale":["*"]}}`), + reqRegsPrivacy: "malformed", expectError: &errortypes.Warning{ Message: "request.regs.ext.us_privacy must contain 4 characters", WarningCode: errortypes.InvalidPrivacyConsentWarningCode, }, }, { - description: "Invalid No Sale Bidders", - reqExt: json.RawMessage(`{"prebid":{"nosale":["*", "another"]}}`), - reqRegsExt: json.RawMessage(`{"us_privacy":"1NYN"}`), - expectError: errors.New("request.ext.prebid.nosale is invalid: can only specify all bidders if no other bidders are provided"), + description: "Invalid No Sale Bidders", + reqExt: json.RawMessage(`{"prebid":{"nosale":["*", "another"]}}`), + reqRegsPrivacy: "1NYN", + expectError: errors.New("request.ext.prebid.nosale is invalid: can only specify all bidders if no other bidders are provided"), }, } for _, test := range testCases { - req := newBidRequest(t) + req := newBidRequest() req.Ext = test.reqExt - req.Regs = &openrtb2.Regs{Ext: test.reqRegsExt} + req.Regs = &openrtb2.Regs{USPrivacy: test.reqRegsPrivacy} var reqExtStruct openrtb_ext.ExtRequest err := jsonutil.UnmarshalValid(req.Ext, &reqExtStruct) @@ -1098,7 +1400,7 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { bidderInfo: config.BidderInfos{}, } - _, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, gdpr.SignalNo, map[string]float64{}) + _, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, gdpr.SignalNo, false, map[string]float64{}) assert.ElementsMatch(t, []error{test.expectError}, errs, test.description) } @@ -1130,7 +1432,7 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { } for _, test := range testCases { - req := newBidRequest(t) + req := newBidRequest() req.Regs = &openrtb2.Regs{COPPA: test.coppa} auctionReq := AuctionRequest{ @@ -1157,7 +1459,7 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { bidderInfo: config.BidderInfos{}, } - bidderRequests, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, map[string]float64{}) + bidderRequests, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, map[string]float64{}) result := bidderRequests[0] assert.Nil(t, errs) @@ -1179,46 +1481,114 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { testCases := []struct { description string inExt json.RawMessage - inSourceExt json.RawMessage + inSChain *openrtb2.SupplyChain outRequestExt json.RawMessage - outSourceExt json.RawMessage + outSource *openrtb2.Source hasError bool + ortbVersion string }{ { description: "nil", inExt: nil, - inSourceExt: nil, + inSChain: nil, outRequestExt: nil, - outSourceExt: nil, + outSource: &openrtb2.Source{ + TID: "testTID", + SChain: nil, + Ext: nil, + }, }, { - description: "ORTB 2.5 chain at source.ext.schain", - inExt: nil, - inSourceExt: json.RawMessage(`{` + seller1SChain + `}`), + description: "Supply Chain defined in request.Source.supplyChain", + inExt: nil, + inSChain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller1.com", + SID: "00001", + RID: "BidRequest1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, outRequestExt: nil, - outSourceExt: json.RawMessage(`{` + seller1SChain + `}`), + outSource: &openrtb2.Source{ + TID: "testTID", + SChain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller1.com", + SID: "00001", + RID: "BidRequest1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, + Ext: nil, + }, + ortbVersion: "2.6", }, { - description: "ORTB 2.5 schain at request.ext.prebid.schains", + description: "Supply Chain defined in request.ext.prebid.schains", inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), - inSourceExt: nil, + inSChain: nil, outRequestExt: nil, - outSourceExt: json.RawMessage(`{` + seller1SChain + `}`), + outSource: &openrtb2.Source{ + TID: "testTID", + SChain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller1.com", + SID: "00001", + RID: "BidRequest1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, + Ext: nil, + }, + ortbVersion: "2.6", }, { - description: "schainwriter instantation error -- multiple bidder schains in ext.prebid.schains.", - inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`), - inSourceExt: json.RawMessage(`{` + seller1SChain + `}`), + description: "schainwriter instantation error -- multiple bidder schains in ext.prebid.schains.", + inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`), + inSChain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller1.com", + SID: "00001", + RID: "BidRequest1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, + outRequestExt: nil, - outSourceExt: nil, + outSource: nil, hasError: true, }, } for _, test := range testCases { - req := newBidRequest(t) - if test.inSourceExt != nil { - req.Source.Ext = test.inSourceExt + req := newBidRequest() + if test.inSChain != nil { + req.Source.SChain = test.inSChain } var extRequest *openrtb_ext.ExtRequest @@ -1247,17 +1617,17 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { privacyConfig: config.Privacy{}, gdprPermsBuilder: gdprPermissionsBuilder, hostSChainNode: nil, - bidderInfo: config.BidderInfos{}, + bidderInfo: config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{Version: test.ortbVersion}}}, } - bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, map[string]float64{}) + bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, false, map[string]float64{}) if test.hasError == true { assert.NotNil(t, errs) assert.Len(t, bidderRequests, 0) } else { result := bidderRequests[0] assert.Nil(t, errs) - assert.Equal(t, test.outSourceExt, result.BidRequest.Source.Ext, test.description+":Source.Ext") + assert.Equal(t, test.outSource, result.BidRequest.Source, test.description+":Source") assert.Equal(t, test.outRequestExt, result.BidRequest.Ext, test.description+":Ext") } } @@ -1291,7 +1661,7 @@ func TestCleanOpenRTBRequestsBidderParams(t *testing.T) { } for _, test := range testCases { - req := newBidRequestWithBidderParams(t) + req := newBidRequestWithBidderParams() var extRequest *openrtb_ext.ExtRequest if test.inExt != nil { req.Ext = test.inExt @@ -1321,7 +1691,7 @@ func TestCleanOpenRTBRequestsBidderParams(t *testing.T) { bidderInfo: config.BidderInfos{}, } - bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, map[string]float64{}) + bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, false, map[string]float64{}) if test.hasError == true { assert.NotNil(t, errs) assert.Len(t, bidderRequests, 0) @@ -1598,86 +1968,95 @@ func TestGetExtCacheInstructions(t *testing.T) { } func TestGetExtTargetData(t *testing.T) { - type inTest struct { - requestExtPrebid *openrtb_ext.ExtRequestPrebid - cacheInstructions extCacheInstructions - } - type outTest struct { - targetData *targetData - nilTargetData bool - } testCases := []struct { - desc string - in inTest - out outTest + name string + givenRequestExtPrebid *openrtb_ext.ExtRequestPrebid + givenCacheInstructions extCacheInstructions + expectTargetData *targetData }{ { - "nil requestExt, nil outTargetData", - inTest{ - requestExtPrebid: nil, - cacheInstructions: extCacheInstructions{ - cacheBids: true, - cacheVAST: true, - }, - }, - outTest{targetData: nil, nilTargetData: true}, + name: "nil", + givenRequestExtPrebid: nil, + givenCacheInstructions: extCacheInstructions{cacheBids: true, cacheVAST: true}, + expectTargetData: nil, }, { - "Valid requestExt, nil Targeting field, nil outTargetData", - inTest{ - requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ - Targeting: nil, - }, - cacheInstructions: extCacheInstructions{ - cacheBids: true, - cacheVAST: true, - }, - }, - outTest{targetData: nil, nilTargetData: true}, + name: "nil-targeting", + givenRequestExtPrebid: &openrtb_ext.ExtRequestPrebid{Targeting: nil}, + givenCacheInstructions: extCacheInstructions{cacheBids: true, cacheVAST: true}, + expectTargetData: nil, }, { - "Valid targeting data in requestExt, valid outTargetData", - inTest{ - requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ - Targeting: &openrtb_ext.ExtRequestTargeting{ - PriceGranularity: &openrtb_ext.PriceGranularity{ - Precision: ptrutil.ToPtr(2), - Ranges: []openrtb_ext.GranularityRange{{Min: 0.00, Max: 5.00, Increment: 1.00}}, - }, - IncludeWinners: ptrutil.ToPtr(true), - IncludeBidderKeys: ptrutil.ToPtr(true), + name: "populated-full", + givenRequestExtPrebid: &openrtb_ext.ExtRequestPrebid{ + Targeting: &openrtb_ext.ExtRequestTargeting{ + AlwaysIncludeDeals: true, + IncludeBidderKeys: ptrutil.ToPtr(true), + IncludeFormat: true, + IncludeWinners: ptrutil.ToPtr(true), + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{}, + PreferDeals: true, + PriceGranularity: &openrtb_ext.PriceGranularity{ + Precision: ptrutil.ToPtr(2), + Ranges: []openrtb_ext.GranularityRange{{Min: 0.00, Max: 5.00, Increment: 1.00}}, }, }, - cacheInstructions: extCacheInstructions{ - cacheBids: true, - cacheVAST: true, + }, + givenCacheInstructions: extCacheInstructions{ + cacheBids: true, + cacheVAST: true, + }, + expectTargetData: &targetData{ + alwaysIncludeDeals: true, + includeBidderKeys: true, + includeCacheBids: true, + includeCacheVast: true, + includeFormat: true, + includeWinners: true, + mediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{}, + preferDeals: true, + priceGranularity: openrtb_ext.PriceGranularity{ + Precision: ptrutil.ToPtr(2), + Ranges: []openrtb_ext.GranularityRange{{Min: 0.00, Max: 5.00, Increment: 1.00}}, }, }, - outTest{ - targetData: &targetData{ - priceGranularity: openrtb_ext.PriceGranularity{ - Precision: ptrutil.ToPtr(2), - Ranges: []openrtb_ext.GranularityRange{{Min: 0.00, Max: 5.00, Increment: 1.00}}, - }, - includeWinners: true, - includeBidderKeys: true, - includeCacheBids: true, - includeCacheVast: true, + }, + { + name: "populated-pointers-nil", + givenRequestExtPrebid: &openrtb_ext.ExtRequestPrebid{ + Targeting: &openrtb_ext.ExtRequestTargeting{ + AlwaysIncludeDeals: true, + IncludeBidderKeys: nil, + IncludeFormat: true, + IncludeWinners: nil, + MediaTypePriceGranularity: nil, + PreferDeals: true, + PriceGranularity: nil, }, - nilTargetData: false, + }, + givenCacheInstructions: extCacheInstructions{ + cacheBids: true, + cacheVAST: true, + }, + expectTargetData: &targetData{ + alwaysIncludeDeals: true, + includeBidderKeys: false, + includeCacheBids: true, + includeCacheVast: true, + includeFormat: true, + includeWinners: false, + mediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{}, + preferDeals: true, + priceGranularity: openrtb_ext.PriceGranularity{}, }, }, } for _, test := range testCases { - actualTargetData := getExtTargetData(test.in.requestExtPrebid, test.in.cacheInstructions) - - if test.out.nilTargetData { - assert.Nil(t, actualTargetData, "%s. Targeting data should be nil. \n", test.desc) - } else { - assert.NotNil(t, actualTargetData, "%s. Targeting data should NOT be nil. \n", test.desc) - assert.Equal(t, *test.out.targetData, *actualTargetData, "%s. Unexpected targeting data value. \n", test.desc) - } + t.Run(test.name, func(t *testing.T) { + result := getExtTargetData(test.givenRequestExtPrebid, test.givenCacheInstructions) + assert.Equal(t, test.expectTargetData, result) + }) } } @@ -1883,7 +2262,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { } for _, test := range testCases { - req := newBidRequest(t) + req := newBidRequest() req.Device.Lmt = test.lmt auctionReq := AuctionRequest{ @@ -1913,7 +2292,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { bidderInfo: config.BidderInfos{}, } - results, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, map[string]float64{}) + results, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, map[string]float64{}) result := results[0] assert.Nil(t, errs) @@ -1930,223 +2309,103 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { func TestCleanOpenRTBRequestsGDPR(t *testing.T) { tcf2Consent := "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA" - trueValue, falseValue := true, false testCases := []struct { description string - gdprAccountEnabled *bool - gdprHostEnabled bool - gdpr string gdprConsent string gdprScrub bool + gdprSignal gdpr.Signal + gdprEnforced bool permissionsError error - gdprDefaultValue string expectPrivacyLabels metrics.PrivacyLabels expectError bool }{ { - description: "Enforce - TCF Invalid", - gdprAccountEnabled: &trueValue, - gdprHostEnabled: true, - gdpr: "1", - gdprConsent: "malformed", - gdprScrub: false, - gdprDefaultValue: "1", + description: "enforce no scrub - TCF invalid", + gdprConsent: "malformed", + gdprScrub: false, + gdprSignal: gdpr.SignalYes, + gdprEnforced: true, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: "", }, }, { - description: "Enforce", - gdprAccountEnabled: &trueValue, - gdprHostEnabled: true, - gdpr: "1", - gdprConsent: tcf2Consent, - gdprScrub: true, - gdprDefaultValue: "1", + description: "enforce and scrub", + gdprConsent: tcf2Consent, + gdprScrub: true, + gdprSignal: gdpr.SignalYes, + gdprEnforced: true, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, }, }, { - description: "Not Enforce", - gdprAccountEnabled: &trueValue, - gdprHostEnabled: true, - gdpr: "0", - gdprConsent: tcf2Consent, - gdprScrub: false, - gdprDefaultValue: "1", + description: "not enforce", + gdprConsent: tcf2Consent, + gdprScrub: false, + gdprSignal: gdpr.SignalYes, + gdprEnforced: false, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, GDPRTCFVersion: "", }, }, { - description: "Enforce; GDPR signal extraction error", - gdprAccountEnabled: &trueValue, - gdprHostEnabled: true, - gdpr: "0{", - gdprConsent: tcf2Consent, - gdprScrub: true, - gdprDefaultValue: "1", + description: "enforce - error while checking if personal info is allowed", + gdprConsent: tcf2Consent, + gdprScrub: true, + permissionsError: errors.New("Some error"), + gdprSignal: gdpr.SignalYes, + gdprEnforced: true, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, }, - expectError: true, }, - { - description: "Enforce; account GDPR enabled, host GDPR setting disregarded", - gdprAccountEnabled: &trueValue, - gdprHostEnabled: false, - gdpr: "1", - gdprConsent: tcf2Consent, - gdprScrub: true, - gdprDefaultValue: "1", - expectPrivacyLabels: metrics.PrivacyLabels{ - GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV2, - }, - }, - { - description: "Not Enforce; account GDPR disabled, host GDPR setting disregarded", - gdprAccountEnabled: &falseValue, - gdprHostEnabled: true, - gdpr: "1", - gdprConsent: tcf2Consent, - gdprScrub: false, - gdprDefaultValue: "1", - expectPrivacyLabels: metrics.PrivacyLabels{ - GDPREnforced: false, - GDPRTCFVersion: "", - }, - }, - { - description: "Enforce; account GDPR not specified, host GDPR enabled", - gdprAccountEnabled: nil, - gdprHostEnabled: true, - gdpr: "1", - gdprConsent: tcf2Consent, - gdprScrub: true, - gdprDefaultValue: "1", - expectPrivacyLabels: metrics.PrivacyLabels{ - GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV2, - }, - }, - { - description: "Not Enforce; account GDPR not specified, host GDPR disabled", - gdprAccountEnabled: nil, - gdprHostEnabled: false, - gdpr: "1", - gdprConsent: tcf2Consent, - gdprScrub: false, - gdprDefaultValue: "1", - expectPrivacyLabels: metrics.PrivacyLabels{ - GDPREnforced: false, - GDPRTCFVersion: "", - }, - }, - { - description: "Enforce - Ambiguous signal, don't sync user if ambiguous", - gdprAccountEnabled: nil, - gdprHostEnabled: true, - gdpr: "null", - gdprConsent: tcf2Consent, - gdprScrub: true, - gdprDefaultValue: "1", - expectPrivacyLabels: metrics.PrivacyLabels{ - GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV2, - }, - }, - { - description: "Not Enforce - Ambiguous signal, sync user if ambiguous", - gdprAccountEnabled: nil, - gdprHostEnabled: true, - gdpr: "null", - gdprConsent: tcf2Consent, - gdprScrub: false, - gdprDefaultValue: "0", - expectPrivacyLabels: metrics.PrivacyLabels{ - GDPREnforced: false, - GDPRTCFVersion: "", - }, - }, - { - description: "Enforce - error while checking if personal info is allowed", - gdprAccountEnabled: nil, - gdprHostEnabled: true, - gdpr: "1", - gdprConsent: tcf2Consent, - gdprScrub: true, - permissionsError: errors.New("Some error"), - gdprDefaultValue: "1", - expectPrivacyLabels: metrics.PrivacyLabels{ - GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV2, - }, - }, - } - - for _, test := range testCases { - req := newBidRequest(t) - req.User.Ext = json.RawMessage(`{"consent":"` + test.gdprConsent + `"}`) - req.Regs = &openrtb2.Regs{ - Ext: json.RawMessage(`{"gdpr":` + test.gdpr + `}`), - } - - privacyConfig := config.Privacy{ - GDPR: config.GDPR{ - DefaultValue: test.gdprDefaultValue, - TCF2: config.TCF2{ - Enabled: test.gdprHostEnabled, - }, - }, - } - - accountConfig := config.Account{ - GDPR: config.AccountGDPR{ - Enabled: test.gdprAccountEnabled, - }, - } - - auctionReq := AuctionRequest{ - BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, - UserSyncs: &emptyUsersync{}, - Account: accountConfig, - TCF2Config: gdpr.NewTCF2Config( - privacyConfig.GDPR.TCF2, - accountConfig.GDPR, - ), - } - - gdprPermissionsBuilder := fakePermissionsBuilder{ - permissions: &permissionsMock{ - allowAllBidders: true, - passGeo: !test.gdprScrub, - passID: !test.gdprScrub, - activitiesError: test.permissionsError, + } + + for _, test := range testCases { + req := newBidRequest() + req.User.Consent = test.gdprConsent + + privacyConfig := config.Privacy{} + accountConfig := config.Account{} + + auctionReq := AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, + UserSyncs: &emptyUsersync{}, + Account: accountConfig, + TCF2Config: gdpr.NewTCF2Config( + privacyConfig.GDPR.TCF2, + accountConfig.GDPR, + ), + } + + gdprPermissionsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + passGeo: !test.gdprScrub, + passID: !test.gdprScrub, + activitiesError: test.permissionsError, }, }.Builder - gdprDefaultValue := gdpr.SignalYes - if test.gdprDefaultValue == "0" { - gdprDefaultValue = gdpr.SignalNo - } + metricsMock := metrics.MetricsEngineMock{} + metricsMock.Mock.On("RecordAdapterBuyerUIDScrubbed", mock.Anything).Return() reqSplitter := &requestSplitter{ bidderToSyncerKey: map[string]string{}, - me: &metrics.MetricsEngineMock{}, + me: &metricsMock, privacyConfig: privacyConfig, gdprPermsBuilder: gdprPermissionsBuilder, hostSChainNode: nil, bidderInfo: config.BidderInfos{}, } - results, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdprDefaultValue, map[string]float64{}) + results, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, test.gdprSignal, test.gdprEnforced, map[string]float64{}) result := results[0] if test.expectError { @@ -2158,9 +2417,11 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { if test.gdprScrub { assert.Equal(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID") assert.Equal(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5") + metricsMock.AssertCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus) } else { assert.NotEqual(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID") assert.NotEqual(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5") + metricsMock.AssertNotCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus) } assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels") } @@ -2198,21 +2459,13 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { } for _, test := range testCases { - req := newBidRequest(t) + req := newBidRequest() req.Regs = &openrtb2.Regs{ Ext: json.RawMessage(`{"gdpr":1}`), } req.Imp[0].Ext = json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}, "rubicon": {}}}}`) - privacyConfig := config.Privacy{ - GDPR: config.GDPR{ - DefaultValue: "0", - TCF2: config.TCF2{ - Enabled: test.gdprEnforced, - }, - }, - } - + privacyConfig := config.Privacy{} accountConfig := config.Account{ GDPR: config.AccountGDPR{ Enabled: nil, @@ -2247,7 +2500,7 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { bidderInfo: config.BidderInfos{}, } - results, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, map[string]float64{}) + results, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalYes, test.gdprEnforced, map[string]float64{}) // extract bidder name from each request in the results bidders := []openrtb_ext.BidderName{} @@ -2270,14 +2523,15 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { func TestCleanOpenRTBRequestsWithOpenRTBDowngrade(t *testing.T) { emptyTCF2Config := gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}) - bidReq := newBidRequest(t) + bidReq := newBidRequest() bidReq.Regs = &openrtb2.Regs{} bidReq.Regs.GPP = "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1NYN" bidReq.Regs.GPPSID = []int8{6} bidReq.User.ID = "" bidReq.User.BuyerUID = "" bidReq.User.Yob = 0 - bidReq.User.Geo = &openrtb2.Geo{Lat: 123.46} + bidReq.User.Gender = "" + bidReq.User.Geo = &openrtb2.Geo{Lat: ptrutil.ToPtr(123.46)} downgradedRegs := *bidReq.Regs downgradedUser := *bidReq.User @@ -2297,14 +2551,14 @@ func TestCleanOpenRTBRequestsWithOpenRTBDowngrade(t *testing.T) { req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidReq}, UserSyncs: &emptyUsersync{}, TCF2Config: emptyTCF2Config}, expectRegs: &downgradedRegs, expectUser: &downgradedUser, - bidderInfos: config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{GPPSupported: false}}}, + bidderInfos: config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{GPPSupported: false, Version: "2.6"}}}, }, { name: "Supported", req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidReq}, UserSyncs: &emptyUsersync{}, TCF2Config: emptyTCF2Config}, expectRegs: bidReq.Regs, expectUser: bidReq.User, - bidderInfos: config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{GPPSupported: true}}}, + bidderInfos: config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{GPPSupported: true, Version: "2.6"}}}, }, } @@ -2334,7 +2588,7 @@ func TestCleanOpenRTBRequestsWithOpenRTBDowngrade(t *testing.T) { hostSChainNode: nil, bidderInfo: test.bidderInfos, } - bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo, map[string]float64{}) + bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo, false, map[string]float64{}) assert.Nil(t, err, "Err should be nil") bidRequest := bidderRequests[0] assert.Equal(t, test.expectRegs, bidRequest.BidRequest.Regs) @@ -2351,145 +2605,151 @@ func TestBuildRequestExtForBidder(t *testing.T) { ) testCases := []struct { - description string + name string requestExt json.RawMessage bidderParams map[string]json.RawMessage alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes expectedJson json.RawMessage }{ { - description: "Nil", + name: "Nil", bidderParams: nil, requestExt: nil, alternateBidderCodes: nil, expectedJson: nil, }, { - description: "Empty", + name: "Empty", bidderParams: nil, alternateBidderCodes: nil, requestExt: json.RawMessage(`{}`), expectedJson: nil, }, { - description: "Prebid - Allowed Fields Only", + name: "Prebid - Allowed Fields Only", bidderParams: nil, requestExt: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`), expectedJson: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`), }, { - description: "Prebid - Allowed Fields + Bidder Params", + name: "Prebid - Allowed Fields + Bidder Params", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, requestExt: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`), expectedJson: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}, "bidderparams":"bar"}}`), }, { - description: "Other", + name: "Other", bidderParams: nil, requestExt: json.RawMessage(`{"other":"foo"}`), expectedJson: json.RawMessage(`{"other":"foo"}`), }, { - description: "Prebid + Other + Bider Params", + name: "Prebid + Other + Bider Params", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}, "bidderparams":"bar"}}`), }, { - description: "Prebid + AlternateBidderCodes in pbs config but current bidder not in AlternateBidderCodes config", + name: "Prebid + AlternateBidderCodes in pbs config but current bidder not in AlternateBidderCodes config", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{"bar": {Enabled: true, AllowedBidderCodes: []string{"*"}}}}, requestExt: json.RawMessage(`{"other":"foo"}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"alternatebiddercodes":{"enabled":true,"bidders":null},"bidderparams":"bar"}}`), }, { - description: "Prebid + AlternateBidderCodes in request", + name: "Prebid + AlternateBidderCodes in request", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{}, requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]},"bar":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}},"bidderparams":"bar"}}`), }, { - description: "Prebid + AlternateBidderCodes in request but current bidder not in AlternateBidderCodes config", + name: "Prebid + AlternateBidderCodes in request but current bidder not in AlternateBidderCodes config", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{}, requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"bar":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":null},"bidderparams":"bar"}}`), }, { - description: "Prebid + AlternateBidderCodes in both pbs config and in the request", + name: "Prebid + AlternateBidderCodes in both pbs config and in the request", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{"foo": {Enabled: true, AllowedBidderCodes: []string{"*"}}}}, requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]},"bar":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}},"bidderparams":"bar"}}`), }, { - description: "Prebid + Other + Bider Params + MultiBid.Bidder", + name: "Prebid + Other + Bider Params + MultiBid.Bidder", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"foo","maxbids":2,"targetbiddercodeprefix":"fmb"},{"bidders":["appnexus","groupm"],"maxbids":2}]}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"foo","maxbids":2,"targetbiddercodeprefix":"fmb"}],"bidderparams":"bar"}}`), }, { - description: "Prebid + Other + Bider Params + MultiBid.Bidders", + name: "Prebid + Other + Bider Params + MultiBid.Bidders", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"pubmatic","maxbids":3,"targetbiddercodeprefix":"pubM"},{"bidders":["foo","groupm"],"maxbids":4}]}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidders":["foo"],"maxbids":4}],"bidderparams":"bar"}}`), }, { - description: "Prebid + Other + Bider Params + MultiBid (foo not in MultiBid)", + name: "Prebid + Other + Bider Params + MultiBid (foo not in MultiBid)", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"foo2","maxbids":2,"targetbiddercodeprefix":"fmb"},{"bidders":["appnexus","groupm"],"maxbids":2}]}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"bidderparams":"bar"}}`), }, { - description: "Prebid + Other + Bider Params + MultiBid (foo not in MultiBid)", + name: "Prebid + Other + Bider Params + MultiBid (foo not in MultiBid)", bidderParams: map[string]json.RawMessage{bidder: bidderParams}, requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"foo2","maxbids":2,"targetbiddercodeprefix":"fmb"},{"bidders":["appnexus","groupm"],"maxbids":2}]}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"bidderparams":"bar"}}`), }, { - description: "Prebid + AlternateBidderCodes.MultiBid.Bidder", + name: "Prebid + AlternateBidderCodes.MultiBid.Bidder", requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["pubmatic"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidder":"foo2","maxbids":4,"targetbiddercodeprefix":"fmb2"},{"bidder":"pubmatic","maxbids":5,"targetbiddercodeprefix":"pm"}]}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["pubmatic"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidder":"pubmatic","maxbids":5,"targetbiddercodeprefix":"pm"}]}}`), }, { - description: "Prebid + AlternateBidderCodes.MultiBid.Bidders", + name: "Prebid + AlternateBidderCodes.MultiBid.Bidders", requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["pubmatic"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic","groupm"],"maxbids":4}]}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["pubmatic"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic"],"maxbids":4}]}}`), }, { - description: "Prebid + AlternateBidderCodes.MultiBid.Bidder with *", + name: "Prebid + AlternateBidderCodes.MultiBid.Bidder with *", requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["*"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidder":"foo2","maxbids":4,"targetbiddercodeprefix":"fmb2"},{"bidder":"pubmatic","maxbids":5,"targetbiddercodeprefix":"pm"}]}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["*"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidder":"foo2","maxbids":4,"targetbiddercodeprefix":"fmb2"},{"bidder":"pubmatic","maxbids":5,"targetbiddercodeprefix":"pm"}]}}`), }, { - description: "Prebid + AlternateBidderCodes.MultiBid.Bidders with *", + name: "Prebid + AlternateBidderCodes.MultiBid.Bidders with *", requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["*"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic","groupm"],"maxbids":4}]}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["*"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic"],"maxbids":4},{"bidders":["groupm"],"maxbids":4}]}}`), }, { - description: "Prebid + AlternateBidderCodes + MultiBid", + name: "Prebid + AlternateBidderCodes + MultiBid", requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}},"multibid":[{"bidder":"foo3","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic","groupm"],"maxbids":4}]}}`), expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}}}}`), }, + { + name: "targeting", + requestExt: json.RawMessage(`{"prebid":{"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"mediatypepricegranularity":{},"includebidderkeys":true,"includewinners":true,"includebrandcategory":{"primaryadserver":1,"publisher":"anyPublisher","withcategory":true}}}}`), + expectedJson: json.RawMessage(`{"prebid":{"targeting":{"includebrandcategory":{"primaryadserver":1,"publisher":"anyPublisher","withcategory":true}}}}`), + }, } for _, test := range testCases { - requestExtParsed := &openrtb_ext.ExtRequest{} - if test.requestExt != nil { - err := jsonutil.UnmarshalValid(test.requestExt, requestExtParsed) - if !assert.NoError(t, err, test.description+":parse_ext") { - continue + t.Run(test.name, func(t *testing.T) { + req := openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: test.requestExt, + }, } - } + err := buildRequestExtForBidder(bidder, &req, test.bidderParams, test.alternateBidderCodes) + assert.NoError(t, req.RebuildRequest()) + assert.NoError(t, err) - actualJson, actualErr := buildRequestExtForBidder(bidder, test.requestExt, requestExtParsed, test.bidderParams, test.alternateBidderCodes) - if len(test.expectedJson) > 0 { - assert.JSONEq(t, string(test.expectedJson), string(actualJson), test.description+":json") - } else { - assert.Equal(t, test.expectedJson, actualJson, test.description+":json") - } - assert.NoError(t, actualErr, test.description+":err") + if len(test.expectedJson) > 0 { + assert.JSONEq(t, string(test.expectedJson), string(req.Ext)) + } else { + assert.Equal(t, test.expectedJson, req.Ext) + } + }) } } @@ -2497,32 +2757,79 @@ func TestBuildRequestExtForBidder_RequestExtParsedNil(t *testing.T) { var ( bidder = "foo" requestExt = json.RawMessage(`{}`) - requestExtParsed *openrtb_ext.ExtRequest bidderParams map[string]json.RawMessage alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes ) - actualJson, actualErr := buildRequestExtForBidder(bidder, requestExt, requestExtParsed, bidderParams, alternateBidderCodes) - assert.Nil(t, actualJson) - assert.NoError(t, actualErr) + req := openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: requestExt, + }, + } + err := buildRequestExtForBidder(bidder, &req, bidderParams, alternateBidderCodes) + assert.NoError(t, req.RebuildRequest()) + assert.Nil(t, req.Ext) + assert.NoError(t, err) } func TestBuildRequestExtForBidder_RequestExtMalformed(t *testing.T) { var ( bidder = "foo" requestExt = json.RawMessage(`malformed`) - requestExtParsed = &openrtb_ext.ExtRequest{} bidderParams map[string]json.RawMessage alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes ) - actualJson, actualErr := buildRequestExtForBidder(bidder, requestExt, requestExtParsed, bidderParams, alternateBidderCodes) - assert.Equal(t, json.RawMessage(nil), actualJson) - assert.EqualError(t, actualErr, "expect { or n, but found m") + req := openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: requestExt, + }, + } + err := buildRequestExtForBidder(bidder, &req, bidderParams, alternateBidderCodes) + assert.NoError(t, req.RebuildRequest()) + assert.EqualError(t, err, "expect { or n, but found m") +} + +func TestBuildRequestExtTargeting(t *testing.T) { + t.Run("nil", func(t *testing.T) { + result := buildRequestExtTargeting(nil) + assert.Nil(t, result) + }) + + t.Run("brandcategory-nil", func(t *testing.T) { + given := &openrtb_ext.ExtRequestTargeting{} + + result := buildRequestExtTargeting(given) + assert.Nil(t, result) + }) + + t.Run("brandcategory-populated", func(t *testing.T) { + brandCatgory := &openrtb_ext.ExtIncludeBrandCategory{ + PrimaryAdServer: 1, + Publisher: "anyPublisher", + WithCategory: true, + TranslateCategories: ptrutil.ToPtr(true), + } + + given := &openrtb_ext.ExtRequestTargeting{ + PriceGranularity: &openrtb_ext.PriceGranularity{}, + IncludeBrandCategory: brandCatgory, + IncludeWinners: ptrutil.ToPtr(true), + } + + expected := &openrtb_ext.ExtRequestTargeting{ + PriceGranularity: nil, + IncludeBrandCategory: brandCatgory, + IncludeWinners: nil, + } + + result := buildRequestExtTargeting(given) + assert.Equal(t, expected, result) + }) } // newAdapterAliasBidRequest builds a BidRequest with aliases -func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest { +func newAdapterAliasBidRequest() *openrtb2.BidRequest { dnt := int8(1) return &openrtb2.BidRequest{ Site: &openrtb2.Site{ @@ -2534,7 +2841,7 @@ func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest { }, Device: &openrtb2.Device{ DIDMD5: "some device ID hash", - UA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", + UA: deviceUA, IFA: "ifa", IP: "132.173.230.74", DNT: &dnt, @@ -2568,7 +2875,7 @@ func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest { } } -func newBidRequest(t *testing.T) *openrtb2.BidRequest { +func newBidRequest() *openrtb2.BidRequest { return &openrtb2.BidRequest{ Site: &openrtb2.Site{ Page: "www.some.domain.com", @@ -2578,11 +2885,17 @@ func newBidRequest(t *testing.T) *openrtb2.BidRequest { }, }, Device: &openrtb2.Device{ - DIDMD5: "some device ID hash", - UA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", - IFA: "ifa", + UA: deviceUA, IP: "132.173.230.74", Language: "EN", + DIDMD5: "DIDMD5", + IFA: "IFA", + DIDSHA1: "DIDSHA1", + DPIDMD5: "DPIDMD5", + DPIDSHA1: "DPIDSHA1", + MACMD5: "MACMD5", + MACSHA1: "MACSHA1", + Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.456), Lon: ptrutil.ToPtr(11.278)}, }, Source: &openrtb2.Source{ TID: "testTID", @@ -2591,8 +2904,13 @@ func newBidRequest(t *testing.T) *openrtb2.BidRequest { ID: "our-id", BuyerUID: "their-id", Yob: 1982, - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{Lat: 123.456}, + Gender: "test", + Ext: json.RawMessage(`{"data": 1, "test": 2}`), + Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.456), Lon: ptrutil.ToPtr(11.278)}, + EIDs: []openrtb2.EID{ + {Source: "eids-source"}, + }, + Data: []openrtb2.Data{{ID: "data-id"}}, }, Imp: []openrtb2.Imp{{ BidFloor: 100, @@ -2611,7 +2929,7 @@ func newBidRequest(t *testing.T) *openrtb2.BidRequest { } } -func newBidRequestWithBidderParams(t *testing.T) *openrtb2.BidRequest { +func newBidRequestWithBidderParams() *openrtb2.BidRequest { return &openrtb2.BidRequest{ Site: &openrtb2.Site{ Page: "www.some.domain.com", @@ -2622,7 +2940,7 @@ func newBidRequestWithBidderParams(t *testing.T) *openrtb2.BidRequest { }, Device: &openrtb2.Device{ DIDMD5: "some device ID hash", - UA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", + UA: deviceUA, IFA: "ifa", IP: "132.173.230.74", Language: "EN", @@ -2693,193 +3011,112 @@ func TestRemoveUnpermissionedEids(t *testing.T) { bidder := "bidderA" testCases := []struct { - description string - userExt json.RawMessage - eidPermissions []openrtb_ext.ExtRequestPrebidDataEidPermission - expectedUserExt json.RawMessage + description string + userEids []openrtb2.EID + eidPermissions []openrtb_ext.ExtRequestPrebidDataEidPermission + expectedUserEids []openrtb2.EID }{ - { - description: "Extension Nil", - userExt: nil, - eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ - {Source: "source1", Bidders: []string{"bidderA"}}, - }, - expectedUserExt: nil, - }, - { - description: "Extension Empty", - userExt: json.RawMessage(`{}`), - eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ - {Source: "source1", Bidders: []string{"bidderA"}}, - }, - expectedUserExt: json.RawMessage(`{}`), - }, - { - description: "Extension Empty - Keep Other Data", - userExt: json.RawMessage(`{"other":42}`), - eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ - {Source: "source1", Bidders: []string{"bidderA"}}, - }, - expectedUserExt: json.RawMessage(`{"other":42}`), - }, + { description: "Eids Empty", - userExt: json.RawMessage(`{"eids":[]}`), - eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ - {Source: "source1", Bidders: []string{"bidderA"}}, - }, - expectedUserExt: json.RawMessage(`{"eids":[]}`), - }, - { - description: "Eids Empty - Keep Other Data", - userExt: json.RawMessage(`{"eids":[],"other":42}`), + userEids: []openrtb2.EID{}, eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source1", Bidders: []string{"bidderA"}}, }, - expectedUserExt: json.RawMessage(`{"eids":[],"other":42}`), + expectedUserEids: []openrtb2.EID{}, }, { - description: "Allowed By Nil Permissions", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), - eidPermissions: nil, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + description: "Allowed By Nil Permissions", + userEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, + eidPermissions: nil, + expectedUserEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, }, { - description: "Allowed By Empty Permissions", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), - eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{}, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + description: "Allowed By Empty Permissions", + userEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, + eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{}, + expectedUserEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, }, { description: "Allowed By Specific Bidder", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + userEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source1", Bidders: []string{"bidderA"}}, }, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + expectedUserEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, }, { description: "Allowed By Specific Bidder - Case Insensitive", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + userEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source1", Bidders: []string{"BIDDERA"}}, }, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + expectedUserEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, }, { description: "Allowed By All Bidders", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + userEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source1", Bidders: []string{"*"}}, }, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + expectedUserEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, }, { description: "Allowed By Lack Of Matching Source", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + userEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source2", Bidders: []string{"otherBidder"}}, }, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), - }, - { - description: "Allowed - Keep Other Data", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}],"other":42}`), - eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ - {Source: "source1", Bidders: []string{"bidderA"}}, - }, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}],"other":42}`), + expectedUserEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, }, { description: "Denied", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), + userEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}}, eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source1", Bidders: []string{"otherBidder"}}, }, - expectedUserExt: nil, + expectedUserEids: nil, }, { - description: "Denied - Keep Other Data", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}],"otherdata":42}`), - eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ - {Source: "source1", Bidders: []string{"otherBidder"}}, + description: "Mix Of Allowed By Specific Bidder, Allowed By Lack Of Matching Source, Denied", + userEids: []openrtb2.EID{ + {Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID1"}}}, + {Source: "source2", UIDs: []openrtb2.UID{{ID: "anyID2"}}}, + {Source: "source3", UIDs: []openrtb2.UID{{ID: "anyID3"}}}, }, - expectedUserExt: json.RawMessage(`{"otherdata":42}`), - }, - { - description: "Mix Of Allowed By Specific Bidder, Allowed By Lack Of Matching Source, Denied, Keep Other Data", - userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID1"}]},{"source":"source2","uids":[{"id":"anyID2"}]},{"source":"source3","uids":[{"id":"anyID3"}]}],"other":42}`), eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ {Source: "source1", Bidders: []string{"bidderA"}}, {Source: "source3", Bidders: []string{"otherBidder"}}, }, - expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID1"}]},{"source":"source2","uids":[{"id":"anyID2"}]}],"other":42}`), + expectedUserEids: []openrtb2.EID{ + {Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID1"}}}, + {Source: "source2", UIDs: []openrtb2.UID{{ID: "anyID2"}}}, + }, }, } for _, test := range testCases { - request := &openrtb2.BidRequest{ - User: &openrtb2.User{Ext: test.userExt}, - } + t.Run(test.description, func(t *testing.T) { + request := &openrtb2.BidRequest{ + User: &openrtb2.User{EIDs: test.userEids}, + } - requestExt := &openrtb_ext.ExtRequest{ - Prebid: openrtb_ext.ExtRequestPrebid{ + reqWrapper := openrtb_ext.RequestWrapper{BidRequest: request} + re, _ := reqWrapper.GetRequestExt() + re.SetPrebid(&openrtb_ext.ExtRequestPrebid{ Data: &openrtb_ext.ExtRequestPrebidData{ EidPermissions: test.eidPermissions, }, - }, - } - - expectedRequest := &openrtb2.BidRequest{ - User: &openrtb2.User{Ext: test.expectedUserExt}, - } - - resultErr := removeUnpermissionedEids(request, bidder, requestExt) - assert.NoError(t, resultErr, test.description) - assert.Equal(t, expectedRequest, request, test.description) - } -} - -func TestRemoveUnpermissionedEidsUnmarshalErrors(t *testing.T) { - testCases := []struct { - description string - userExt json.RawMessage - expectedErr string - }{ - { - description: "Malformed Ext", - userExt: json.RawMessage(`malformed`), - expectedErr: "expect { or n, but found m", - }, - { - description: "Malformed Eid Array Type", - userExt: json.RawMessage(`{"eids":[42]}`), - expectedErr: "cannot unmarshal []openrtb2.EID: expect { or n, but found 4", - }, - { - description: "Malformed Eid Item Type", - userExt: json.RawMessage(`{"eids":[{"source":42,"id":"anyID"}]}`), - expectedErr: "cannot unmarshal openrtb2.EID.Source: expects \" or n, but found 4", - }, - } - - for _, test := range testCases { - request := &openrtb2.BidRequest{ - User: &openrtb2.User{Ext: test.userExt}, - } + }) - requestExt := &openrtb_ext.ExtRequest{ - Prebid: openrtb_ext.ExtRequestPrebid{ - Data: &openrtb_ext.ExtRequestPrebidData{ - EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ - {Source: "source1", Bidders: []string{"*"}}, - }, - }, - }, - } + expectedRequest := &openrtb2.BidRequest{ + User: &openrtb2.User{EIDs: test.expectedUserEids}, + } - resultErr := removeUnpermissionedEids(request, "bidderA", requestExt) - assert.EqualError(t, resultErr, test.expectedErr, test.description) + resultErr := removeUnpermissionedEids(&reqWrapper, bidder) + assert.NoError(t, resultErr, test.description) + assert.Equal(t, expectedRequest, reqWrapper.BidRequest) + }) } } @@ -2997,23 +3234,17 @@ func TestGetDebugInfo(t *testing.T) { func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) { testCases := []struct { - description string - request *openrtb2.BidRequest - requestExt *openrtb_ext.ExtRequest + description string + request *openrtb2.BidRequest + eidPermissions []openrtb_ext.ExtRequestPrebidDataEidPermission }{ { description: "Nil User", request: &openrtb2.BidRequest{ User: nil, }, - requestExt: &openrtb_ext.ExtRequest{ - Prebid: openrtb_ext.ExtRequestPrebid{ - Data: &openrtb_ext.ExtRequestPrebidData{ - EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ - {Source: "source1", Bidders: []string{"*"}}, - }, - }, - }, + eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ + {Source: "source1", Bidders: []string{"*"}}, }, }, { @@ -3021,14 +3252,8 @@ func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) { request: &openrtb2.BidRequest{ User: &openrtb2.User{}, }, - requestExt: &openrtb_ext.ExtRequest{ - Prebid: openrtb_ext.ExtRequestPrebid{ - Data: &openrtb_ext.ExtRequestPrebidData{ - EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ - {Source: "source1", Bidders: []string{"*"}}, - }, - }, - }, + eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ + {Source: "source1", Bidders: []string{"*"}}, }, }, { @@ -3036,27 +3261,25 @@ func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) { request: &openrtb2.BidRequest{ User: &openrtb2.User{Ext: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`)}, }, - requestExt: nil, - }, - { - description: "Nil Prebid Data", - request: &openrtb2.BidRequest{ - User: &openrtb2.User{Ext: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`)}, - }, - requestExt: &openrtb_ext.ExtRequest{ - Prebid: openrtb_ext.ExtRequestPrebid{ - Data: nil, - }, - }, }, } for _, test := range testCases { - requestExpected := *test.request + t.Run(test.description, func(t *testing.T) { + requestExpected := *test.request + reqWrapper := openrtb_ext.RequestWrapper{BidRequest: test.request} + + re, _ := reqWrapper.GetRequestExt() + re.SetPrebid(&openrtb_ext.ExtRequestPrebid{ + Data: &openrtb_ext.ExtRequestPrebidData{ + EidPermissions: test.eidPermissions, + }, + }) - resultErr := removeUnpermissionedEids(test.request, "bidderA", test.requestExt) - assert.NoError(t, resultErr, test.description+":err") - assert.Equal(t, &requestExpected, test.request, test.description+":request") + resultErr := removeUnpermissionedEids(&reqWrapper, "bidderA") + assert.NoError(t, resultErr, test.description+":err") + assert.Equal(t, &requestExpected, reqWrapper.BidRequest, test.description+":request") + }) } } @@ -3091,28 +3314,57 @@ func TestCleanOpenRTBRequestsSChainMultipleBidders(t *testing.T) { }, }.Builder + ortb26enabled := config.BidderInfo{OpenRTB: &config.OpenRTBInfo{Version: "2.6"}} reqSplitter := &requestSplitter{ bidderToSyncerKey: map[string]string{}, me: &metrics.MetricsEngineMock{}, privacyConfig: config.Privacy{}, gdprPermsBuilder: gdprPermissionsBuilder, hostSChainNode: nil, - bidderInfo: config.BidderInfos{}, + bidderInfo: config.BidderInfos{"appnexus": ortb26enabled, "axonix": ortb26enabled}, } - bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, map[string]float64{}) + bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, false, map[string]float64{}) assert.Nil(t, errs) assert.Len(t, bidderRequests, 2, "Bid request count is not 2") - bidRequestSourceExts := map[openrtb_ext.BidderName]json.RawMessage{} + bidRequestSourceSupplyChain := map[openrtb_ext.BidderName]*openrtb2.SupplyChain{} for _, bidderRequest := range bidderRequests { - bidRequestSourceExts[bidderRequest.BidderName] = bidderRequest.BidRequest.Source.Ext + bidRequestSourceSupplyChain[bidderRequest.BidderName] = bidderRequest.BidRequest.Source.SChain + } + + appnexusSchainsSchainExpected := &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller1.com", + SID: "00001", + RID: "BidRequest1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + } + + axonixSchainsSchainExpected := &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller2.com", + SID: "00002", + RID: "BidRequest2", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, } - appnexusPrebidSchainsSchain := json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`) - axonixPrebidSchainsSchain := json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}`) - assert.Equal(t, appnexusPrebidSchainsSchain, bidRequestSourceExts["appnexus"], "Incorrect appnexus bid request schain in source.ext") - assert.Equal(t, axonixPrebidSchainsSchain, bidRequestSourceExts["axonix"], "Incorrect axonix bid request schain in source.ext") + assert.Equal(t, appnexusSchainsSchainExpected, bidRequestSourceSupplyChain["appnexus"], "Incorrect appnexus bid request schain ") + assert.Equal(t, axonixSchainsSchainExpected, bidRequestSourceSupplyChain["axonix"], "Incorrect axonix bid request schain") } func TestCleanOpenRTBRequestsBidAdjustment(t *testing.T) { @@ -3161,7 +3413,7 @@ func TestCleanOpenRTBRequestsBidAdjustment(t *testing.T) { }}, }, { - description: "bidAjustement Not provided", + description: "bidAdjustment Not provided", gdprAccountEnabled: &falseValue, gdprHostEnabled: true, gdpr: "1", @@ -3190,7 +3442,7 @@ func TestCleanOpenRTBRequestsBidAdjustment(t *testing.T) { }, } for _, test := range testCases { - req := newBidRequest(t) + req := newBidRequest() accountConfig := config.Account{ GDPR: config.AccountGDPR{ Enabled: &falseValue, @@ -3203,6 +3455,7 @@ func TestCleanOpenRTBRequestsBidAdjustment(t *testing.T) { BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, UserSyncs: &emptyUsersync{}, Account: accountConfig, + TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), } gdprPermissionsBuilder := fakePermissionsBuilder{ permissions: &permissionsMock{ @@ -3219,50 +3472,211 @@ func TestCleanOpenRTBRequestsBidAdjustment(t *testing.T) { hostSChainNode: nil, bidderInfo: config.BidderInfos{}, } - results, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, test.bidAdjustmentFactor) + results, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, test.bidAdjustmentFactor) result := results[0] assert.Nil(t, errs) assert.Equal(t, test.expectedImp, result.BidRequest.Imp, test.description) } } -func TestApplyFPD(t *testing.T) { - testCases := []struct { - description string - inputFpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData - inputBidderName string - inputBidderCoreName string - inputBidderIsRequestAlias bool - inputRequest openrtb2.BidRequest - expectedRequest openrtb2.BidRequest +func TestCleanOpenRTBRequestsBuyerUID(t *testing.T) { + tcf2Consent := "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA" + + buyerUIDAppnexus := `{"appnexus": "a"}` + buyerUIDAppnexusMixedCase := `{"aPpNeXuS": "a"}` + buyerUIDBoth := `{"appnexus": "a", "pubmatic": "b"}` + + bidderParamsAppnexus := `{"appnexus": {"placementId": 1}}` + bidderParamsBoth := `{"appnexus": {"placementId": 1}, "pubmatic": {"publisherId": "abc"}}` + + tests := []struct { + name string + bidderParams string + user openrtb2.User + expectedUsers map[string]openrtb2.User }{ { - description: "fpd-nil", - inputFpd: nil, - inputBidderName: "bidderFromRequest", - inputBidderCoreName: "bidderNormalized", - inputBidderIsRequestAlias: false, - inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, - expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + name: "one-bidder-with-prebid-buyeruid", + bidderParams: bidderParamsAppnexus, + user: openrtb2.User{ + ID: "some-id", + Ext: json.RawMessage(`{"data": 1, "test": 2, "prebid": {"buyeruids": ` + buyerUIDAppnexus + `}}`), + Consent: tcf2Consent, + }, + expectedUsers: map[string]openrtb2.User{ + "appnexus": { + ID: "some-id", + BuyerUID: "a", + Ext: json.RawMessage(`{"consent":"` + tcf2Consent + `","data":1,"test":2}`), + }, + }, }, { - description: "fpd-bidderdata-nil", - inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ - "bidderNormalized": nil, + name: "one-bidder-with-prebid-buyeruid-mixed-case", + bidderParams: bidderParamsAppnexus, + user: openrtb2.User{ + ID: "some-id", + Ext: json.RawMessage(`{"data": 1, "test": 2, "prebid": {"buyeruids": ` + buyerUIDAppnexusMixedCase + `}}`), + Consent: tcf2Consent, + }, + expectedUsers: map[string]openrtb2.User{ + "appnexus": { + ID: "some-id", + BuyerUID: "a", + Ext: json.RawMessage(`{"consent":"` + tcf2Consent + `","data":1,"test":2}`), + }, }, - inputBidderName: "bidderFromRequest", - inputBidderCoreName: "bidderNormalized", - inputBidderIsRequestAlias: false, - inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, - expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, }, { - description: "fpd-bidderdata-notdefined", - inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ - "differentBidder": {App: &openrtb2.App{ID: "AppId"}}, + name: "one-bidder-with-buyeruid-already-set", + bidderParams: bidderParamsAppnexus, + user: openrtb2.User{ + ID: "some-id", + BuyerUID: "already-set-buyeruid", + Ext: json.RawMessage(`{"data": 1, "test": 2, "prebid": {"buyeruids": ` + buyerUIDAppnexus + `}}`), + Consent: tcf2Consent, }, - inputBidderName: "bidderFromRequest", - inputBidderCoreName: "bidderNormalized", + expectedUsers: map[string]openrtb2.User{ + "appnexus": { + ID: "some-id", + BuyerUID: "already-set-buyeruid", + Ext: json.RawMessage(`{"consent":"` + tcf2Consent + `","data":1,"test":2}`), + }, + }, + }, + { + name: "two-bidder-with-prebid-buyeruids", + bidderParams: bidderParamsBoth, + user: openrtb2.User{ + ID: "some-id", + Ext: json.RawMessage(`{"data": 1, "test": 2, "prebid": {"buyeruids": ` + buyerUIDBoth + `}}`), + Consent: tcf2Consent, + }, + expectedUsers: map[string]openrtb2.User{ + "appnexus": { + ID: "some-id", + BuyerUID: "a", + Ext: json.RawMessage(`{"consent":"` + tcf2Consent + `","data":1,"test":2}`), + }, + "pubmatic": { + ID: "some-id", + BuyerUID: "b", + Ext: json.RawMessage(`{"consent":"` + tcf2Consent + `","data":1,"test":2}`), + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + + req := &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: "some-publisher-id", + }, + }, + Imp: []openrtb2.Imp{{ + ID: "some-imp-id", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ + W: 300, + H: 250, + }}, + }, + Ext: json.RawMessage(`{"prebid":{"tid":"123", "bidder":` + string(test.bidderParams) + `}}`), + }}, + User: &test.user, + } + + auctionReq := AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, + UserSyncs: &emptyUsersync{}, + Account: config.Account{ + GDPR: config.AccountGDPR{ + Enabled: ptrutil.ToPtr(false), + }, + }, + TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + } + gdprPermissionsBuilder := fakePermissionsBuilder{ + permissions: &permissionsMock{ + allowAllBidders: true, + passGeo: false, + passID: false, + activitiesError: nil, + }, + }.Builder + + reqSplitter := &requestSplitter{ + bidderToSyncerKey: map[string]string{}, + me: &metrics.MetricsEngineMock{}, + gdprPermsBuilder: gdprPermissionsBuilder, + hostSChainNode: nil, + bidderInfo: config.BidderInfos{ + "appnexus": config.BidderInfo{ + OpenRTB: &config.OpenRTBInfo{ + Version: "2.5", + }, + }, + "pubmatic": config.BidderInfo{ + OpenRTB: &config.OpenRTBInfo{ + Version: "2.5", + }, + }, + }, + } + + results, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, nil) + + assert.Empty(t, errs) + for _, v := range results { + require.NotNil(t, v.BidRequest, "bidrequest") + require.NotNil(t, v.BidRequest.User, "bidrequest.user") + assert.Equal(t, test.expectedUsers[string(v.BidderName)], *v.BidRequest.User) + } + }) + } +} + +func TestApplyFPD(t *testing.T) { + testCases := []struct { + description string + inputFpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData + inputBidderName string + inputBidderCoreName string + inputBidderIsRequestAlias bool + inputRequest openrtb2.BidRequest + expectedRequest openrtb2.BidRequest + fpdUserEIDsExisted bool + }{ + { + description: "fpd-nil", + inputFpd: nil, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + }, + { + description: "fpd-bidderdata-nil", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": nil, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + }, + { + description: "fpd-bidderdata-notdefined", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "differentBidder": {App: &openrtb2.App{ID: "AppId"}}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", inputBidderIsRequestAlias: false, inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, @@ -3356,77 +3770,122 @@ func TestApplyFPD(t *testing.T) { inputRequest: openrtb2.BidRequest{}, expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", BuyerUID: "FPDBuyerUID"}}, }, + { + description: "req.User is defined and had bidder fpd user eids (fpdUserEIDsExisted); bidderFPD.User defined and has EIDs. Expect to see user.EIDs in result request taken from fpd", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", EIDs: []openrtb2.EID{{Source: "source1"}, {Source: "source2"}}}}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId", EIDs: []openrtb2.EID{{Source: "source3"}, {Source: "source4"}}}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", EIDs: []openrtb2.EID{{Source: "source1"}, {Source: "source2"}}}}, + fpdUserEIDsExisted: true, + }, + { + description: "req.User is defined and doesn't have fpr user eids (fpdUserEIDsExisted); bidderFPD.User defined and has EIDs. Expect to see user.EIDs in result request taken from original req", + inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ + "bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", EIDs: []openrtb2.EID{{Source: "source1"}, {Source: "source2"}}}}, + }, + inputBidderName: "bidderFromRequest", + inputBidderCoreName: "bidderNormalized", + inputBidderIsRequestAlias: false, + inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId", EIDs: []openrtb2.EID{{Source: "source3"}, {Source: "source4"}}}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", EIDs: []openrtb2.EID{{Source: "source3"}, {Source: "source4"}}}}, + fpdUserEIDsExisted: false, + }, } for _, testCase := range testCases { - bidderRequest := BidderRequest{ - BidderName: openrtb_ext.BidderName(testCase.inputBidderName), - BidderCoreName: openrtb_ext.BidderName(testCase.inputBidderCoreName), - IsRequestAlias: testCase.inputBidderIsRequestAlias, - BidRequest: &testCase.inputRequest, - } - applyFPD(testCase.inputFpd, bidderRequest) - assert.Equal(t, testCase.expectedRequest, testCase.inputRequest, fmt.Sprintf("incorrect request after applying fpd, testcase %s", testCase.description)) + t.Run(testCase.description, func(t *testing.T) { + reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: &testCase.inputRequest} + applyFPD( + testCase.inputFpd, + openrtb_ext.BidderName(testCase.inputBidderCoreName), + openrtb_ext.BidderName(testCase.inputBidderName), + testCase.inputBidderIsRequestAlias, + reqWrapper, + testCase.fpdUserEIDsExisted, + ) + assert.Equal(t, &testCase.expectedRequest, reqWrapper.BidRequest) + }) } } -func Test_parseAliasesGVLIDs(t *testing.T) { - type args struct { - orig *openrtb2.BidRequest - } +func TestGetRequestAliases(t *testing.T) { tests := []struct { - name string - args args - want map[string]uint16 - wantError bool + name string + givenRequest openrtb_ext.RequestWrapper + wantAliases map[string]string + wantGVLIDs map[string]uint16 + wantError string }{ { - "AliasGVLID Parsed Correctly", - args{ - orig: &openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"aliases":{"somealiascode":"appnexus"}, "aliasgvlids":{"somealiascode":1}}}`), + name: "nil", + givenRequest: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + wantAliases: nil, + wantGVLIDs: nil, + wantError: "", + }, + { + name: "empty", + givenRequest: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{}`), }, }, - map[string]uint16{"somealiascode": 1}, - false, + wantAliases: nil, + wantGVLIDs: nil, + wantError: "", }, { - "AliasGVLID parsing error", - args{ - orig: &openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"aliases":{"somealiascode":"appnexus"}, "aliasgvlids": {"somealiascode":"abc"}`), + name: "empty-prebid", + givenRequest: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{}}`), }, }, - nil, - true, + wantAliases: nil, + wantGVLIDs: nil, + wantError: "", }, { - "Invalid AliasGVLID", - args{ - orig: &openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"aliases":{"somealiascode":"appnexus"}, "aliasgvlids":"abc"}`), + name: "aliases-and-gvlids", + givenRequest: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"aliases":{"alias1":"bidder1"}, "aliasgvlids":{"alias1":1}}}`), }, }, - nil, - true, + wantAliases: map[string]string{"alias1": "bidder1"}, + wantGVLIDs: map[string]uint16{"alias1": 1}, + wantError: "", }, { - "Missing AliasGVLID", - args{ - orig: &openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"aliases":{"somealiascode":"appnexus"}}`), + name: "malformed", + givenRequest: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`malformed`), }, }, - nil, - false, + wantAliases: nil, + wantGVLIDs: nil, + wantError: "request.ext is invalid", }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := parseAliasesGVLIDs(tt.args.orig) - assert.Equal(t, tt.want, got, "parseAliasesGVLIDs() got = %v, want %v", got, tt.want) - if !tt.wantError && err != nil { - t.Errorf("parseAliasesGVLIDs() expected error got nil") + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + gotAliases, gotGVLIDs, err := getRequestAliases(&test.givenRequest) + + assert.Equal(t, test.wantAliases, gotAliases, "aliases") + assert.Equal(t, test.wantGVLIDs, gotGVLIDs, "gvlids") + + if len(test.wantError) > 0 { + require.Len(t, err, 1, "error-len") + assert.EqualError(t, err[0], test.wantError, "error") + } else { + assert.Empty(t, err, "error") } }) } @@ -3614,7 +4073,7 @@ func TestCleanOpenRTBRequestsFilterBidderRequestExt(t *testing.T) { } for _, test := range testCases { - req := newBidRequestWithBidderParams(t) + req := newBidRequestWithBidderParams() req.Ext = nil var extRequest *openrtb_ext.ExtRequest if test.inExt != nil { @@ -3645,7 +4104,7 @@ func TestCleanOpenRTBRequestsFilterBidderRequestExt(t *testing.T) { bidderInfo: config.BidderInfos{}, } - bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, map[string]float64{}) + bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, false, map[string]float64{}) assert.Equal(t, test.wantError, len(errs) != 0, test.desc) sort.Slice(bidderRequests, func(i, j int) bool { return bidderRequests[i].BidderCoreName < bidderRequests[j].BidderCoreName @@ -3669,24 +4128,34 @@ func (gs GPPMockSection) GetValue() string { return gs.value } +func (gs GPPMockSection) Encode(bool) []byte { + return nil +} + func TestGdprFromGPP(t *testing.T) { testCases := []struct { name string - initialRequest *openrtb2.BidRequest + initialRequest *openrtb_ext.RequestWrapper gpp gpplib.GppContainer - expectedRequest *openrtb2.BidRequest + expectedRequest *openrtb_ext.RequestWrapper }{ { - name: "Empty", // Empty Request - initialRequest: &openrtb2.BidRequest{}, - gpp: gpplib.GppContainer{}, - expectedRequest: &openrtb2.BidRequest{}, + name: "Empty", // Empty Request + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + gpp: gpplib.GppContainer{}, + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, }, { name: "GDPR_Downgrade", // GDPR from GPP, into empty - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + }, }, }, gpp: gpplib.GppContainer{ @@ -3698,25 +4167,29 @@ func TestGdprFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, - GDPR: ptrutil.ToPtr[int8](1), - }, - User: &openrtb2.User{ - Consent: "GDPRConsent", + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + GDPR: ptrutil.ToPtr[int8](1), + }, + User: &openrtb2.User{ + Consent: "GDPRConsent", + }, }, }, }, { name: "GDPR_Downgrade", // GDPR from GPP, into empty legacy, existing objects - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, - USPrivacy: "LegacyUSP", - }, - User: &openrtb2.User{ - ID: "1234", + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + USPrivacy: "LegacyUSP", + }, + User: &openrtb2.User{ + ID: "1234", + }, }, }, gpp: gpplib.GppContainer{ @@ -3728,27 +4201,31 @@ func TestGdprFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, - GDPR: ptrutil.ToPtr[int8](1), - USPrivacy: "LegacyUSP", - }, - User: &openrtb2.User{ - ID: "1234", - Consent: "GDPRConsent", + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + GDPR: ptrutil.ToPtr[int8](1), + USPrivacy: "LegacyUSP", + }, + User: &openrtb2.User{ + ID: "1234", + Consent: "GDPRConsent", + }, }, }, }, { name: "Downgrade_Blocked_By_Existing", // GDPR from GPP blocked by existing GDPR", - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, - GDPR: ptrutil.ToPtr[int8](1), - }, - User: &openrtb2.User{ - Consent: "LegacyConsent", + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + GDPR: ptrutil.ToPtr[int8](1), + }, + User: &openrtb2.User{ + Consent: "LegacyConsent", + }, }, }, gpp: gpplib.GppContainer{ @@ -3760,22 +4237,26 @@ func TestGdprFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, - GDPR: ptrutil.ToPtr[int8](1), - }, - User: &openrtb2.User{ - Consent: "LegacyConsent", + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + GDPR: ptrutil.ToPtr[int8](1), + }, + User: &openrtb2.User{ + Consent: "LegacyConsent", + }, }, }, }, { name: "Downgrade_Partial", // GDPR from GPP partially blocked by existing GDPR - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, - GDPR: ptrutil.ToPtr[int8](0), + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + GDPR: ptrutil.ToPtr[int8](0), + }, }, }, gpp: gpplib.GppContainer{ @@ -3787,21 +4268,25 @@ func TestGdprFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, - GDPR: ptrutil.ToPtr[int8](0), - }, - User: &openrtb2.User{ - Consent: "GDPRConsent", + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + GDPR: ptrutil.ToPtr[int8](0), + }, + User: &openrtb2.User{ + Consent: "GDPRConsent", + }, }, }, }, { name: "No_GDPR", // Downgrade not possible due to missing GDPR - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{6}, + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{6}, + }, }, }, gpp: gpplib.GppContainer{ @@ -3813,18 +4298,22 @@ func TestGdprFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{6}, - GDPR: ptrutil.ToPtr[int8](0), + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{6}, + GDPR: ptrutil.ToPtr[int8](0), + }, }, }, }, { name: "No_SID", // GDPR from GPP partially blocked by no SID - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{6}, + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{6}, + }, }, }, gpp: gpplib.GppContainer{ @@ -3840,19 +4329,23 @@ func TestGdprFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{6}, - GDPR: ptrutil.ToPtr[int8](0), - }, - User: &openrtb2.User{ - Consent: "GDPRConsent", + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{6}, + GDPR: ptrutil.ToPtr[int8](0), + }, + User: &openrtb2.User{ + Consent: "GDPRConsent", + }, }, }, }, { - name: "GDPR_Nil_SID", // GDPR from GPP, into empty, but with nil SID - initialRequest: &openrtb2.BidRequest{}, + name: "GDPR_Nil_SID", // GDPR from GPP, into empty, but with nil SID + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, gpp: gpplib.GppContainer{ SectionTypes: []constants.SectionID{2}, Sections: []gpplib.Section{ @@ -3862,20 +4355,24 @@ func TestGdprFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - User: &openrtb2.User{ - Consent: "GDPRConsent", + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ + Consent: "GDPRConsent", + }, }, }, }, { name: "Downgrade_Nil_SID_Blocked_By_Existing", // GDPR from GPP blocked by existing GDPR, with nil SID", - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GDPR: ptrutil.ToPtr[int8](1), - }, - User: &openrtb2.User{ - Consent: "LegacyConsent", + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GDPR: ptrutil.ToPtr[int8](1), + }, + User: &openrtb2.User{ + Consent: "LegacyConsent", + }, }, }, gpp: gpplib.GppContainer{ @@ -3887,12 +4384,14 @@ func TestGdprFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GDPR: ptrutil.ToPtr[int8](1), - }, - User: &openrtb2.User{ - Consent: "LegacyConsent", + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GDPR: ptrutil.ToPtr[int8](1), + }, + User: &openrtb2.User{ + Consent: "LegacyConsent", + }, }, }, }, @@ -3909,21 +4408,27 @@ func TestGdprFromGPP(t *testing.T) { func TestPrivacyFromGPP(t *testing.T) { testCases := []struct { name string - initialRequest *openrtb2.BidRequest + initialRequest *openrtb_ext.RequestWrapper gpp gpplib.GppContainer - expectedRequest *openrtb2.BidRequest + expectedRequest *openrtb_ext.RequestWrapper }{ { - name: "Empty", // Empty Request - initialRequest: &openrtb2.BidRequest{}, - gpp: gpplib.GppContainer{}, - expectedRequest: &openrtb2.BidRequest{}, + name: "Empty", // Empty Request + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, + gpp: gpplib.GppContainer{}, + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{}, + }, }, { name: "Privacy_Downgrade", // US Privacy from GPP, into empty - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{6}, + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{6}, + }, }, }, gpp: gpplib.GppContainer{ @@ -3935,19 +4440,23 @@ func TestPrivacyFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{6}, - USPrivacy: "USPrivacy", + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{6}, + USPrivacy: "USPrivacy", + }, }, }, }, { name: "Downgrade_Blocked_By_Existing", // US Privacy from GPP blocked by existing US Privacy - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{6}, - USPrivacy: "LegacyPrivacy", + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{6}, + USPrivacy: "LegacyPrivacy", + }, }, }, gpp: gpplib.GppContainer{ @@ -3959,18 +4468,22 @@ func TestPrivacyFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{6}, - USPrivacy: "LegacyPrivacy", + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{6}, + USPrivacy: "LegacyPrivacy", + }, }, }, }, { name: "No_USPrivacy", // Downgrade not possible due to missing USPrivacy - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + }, }, }, gpp: gpplib.GppContainer{ @@ -3982,17 +4495,21 @@ func TestPrivacyFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + }, }, }, }, { name: "No_SID", // US Privacy from GPP partially blocked by no SID - initialRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, + initialRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + }, }, }, gpp: gpplib.GppContainer{ @@ -4008,9 +4525,11 @@ func TestPrivacyFromGPP(t *testing.T) { }, }, }, - expectedRequest: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{ - GPPSID: []int8{2}, + expectedRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + GPPSID: []int8{2}, + }, }, }, }, @@ -4464,126 +4983,228 @@ func TestGetMediaTypeForBid(t *testing.T) { } func TestCleanOpenRTBRequestsActivities(t *testing.T) { + expectedUserDefault := openrtb2.User{ + ID: "our-id", + BuyerUID: "their-id", + Yob: 1982, + Gender: "test", + Ext: json.RawMessage(`{"data": 1, "test": 2}`), + Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.456), Lon: ptrutil.ToPtr(11.278)}, + EIDs: []openrtb2.EID{ + {Source: "eids-source"}, + }, + Data: []openrtb2.Data{{ID: "data-id"}}, + } + expectedDeviceDefault := openrtb2.Device{ + UA: deviceUA, + IP: "132.173.230.74", + Language: "EN", + DIDMD5: "DIDMD5", + IFA: "IFA", + DIDSHA1: "DIDSHA1", + DPIDMD5: "DPIDMD5", + DPIDSHA1: "DPIDSHA1", + MACMD5: "MACMD5", + MACSHA1: "MACSHA1", + Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.456), Lon: ptrutil.ToPtr(11.278)}, + } + + expectedSourceDefault := openrtb2.Source{ + TID: "testTID", + } + testCases := []struct { - name string - req *openrtb2.BidRequest - privacyConfig config.AccountPrivacy - componentName string - allow bool - expectedReqNumber int - expectedUserYOB int64 - expectedUserLat float64 - expectedDeviceDIDMD5 string - expectedSourceTID string + name string + req *openrtb2.BidRequest + privacyConfig config.AccountPrivacy + componentName string + allow bool + ortbVersion string + expectedReqNumber int + expectedUser openrtb2.User + expectUserScrub bool + expectedDevice openrtb2.Device + expectedSource openrtb2.Source + expectedImpExt json.RawMessage }{ { - name: "fetch_bids_request_with_one_bidder_allowed", - req: newBidRequest(t), - privacyConfig: getFetchBidsActivityConfig("appnexus", true), - expectedReqNumber: 1, - expectedUserYOB: 1982, - expectedUserLat: 123.456, - expectedDeviceDIDMD5: "some device ID hash", - expectedSourceTID: "testTID", - }, - { - name: "fetch_bids_request_with_one_bidder_not_allowed", - req: newBidRequest(t), - privacyConfig: getFetchBidsActivityConfig("appnexus", false), - expectedReqNumber: 0, - expectedUserYOB: 1982, - expectedUserLat: 123.456, - expectedDeviceDIDMD5: "some device ID hash", - expectedSourceTID: "testTID", - }, - { - name: "transmit_ufpd_allowed", - req: newBidRequest(t), - privacyConfig: getTransmitUFPDActivityConfig("appnexus", true), - expectedReqNumber: 1, - expectedUserYOB: 1982, - expectedUserLat: 123.456, - expectedDeviceDIDMD5: "some device ID hash", - expectedSourceTID: "testTID", - }, - { - name: "transmit_ufpd_deny", - req: newBidRequest(t), - privacyConfig: getTransmitUFPDActivityConfig("appnexus", false), - expectedReqNumber: 1, - expectedUserYOB: 0, - expectedUserLat: 123.456, - expectedDeviceDIDMD5: "", - expectedSourceTID: "testTID", - }, - { - name: "transmit_precise_geo_allowed", - req: newBidRequest(t), - privacyConfig: getTransmitPreciseGeoActivityConfig("appnexus", true), - expectedReqNumber: 1, - expectedUserYOB: 1982, - expectedUserLat: 123.456, - expectedDeviceDIDMD5: "some device ID hash", - expectedSourceTID: "testTID", - }, - { - name: "transmit_precise_geo_deny", - req: newBidRequest(t), - privacyConfig: getTransmitPreciseGeoActivityConfig("appnexus", false), - expectedReqNumber: 1, - expectedUserYOB: 1982, - expectedUserLat: 123.46, - expectedDeviceDIDMD5: "some device ID hash", - expectedSourceTID: "testTID", - }, - { - name: "transmit_tid_allowed", - req: newBidRequest(t), - privacyConfig: getTransmitTIDActivityConfig("appnexus", true), - expectedReqNumber: 1, - expectedUserYOB: 1982, - expectedUserLat: 123.456, - expectedDeviceDIDMD5: "some device ID hash", - expectedSourceTID: "testTID", - }, - { - name: "transmit_tid_deny", - req: newBidRequest(t), - privacyConfig: getTransmitTIDActivityConfig("appnexus", false), - expectedReqNumber: 1, - expectedUserYOB: 1982, - expectedUserLat: 123.456, - expectedDeviceDIDMD5: "some device ID hash", - expectedSourceTID: "", + name: "fetch_bids_request_with_one_bidder_allowed", + req: newBidRequest(), + privacyConfig: getFetchBidsActivityConfig("appnexus", true), + ortbVersion: "2.6", + expectedReqNumber: 1, + expectedUser: expectedUserDefault, + expectedDevice: expectedDeviceDefault, + expectedSource: expectedSourceDefault, + }, + { + name: "fetch_bids_request_with_one_bidder_not_allowed", + req: newBidRequest(), + privacyConfig: getFetchBidsActivityConfig("appnexus", false), + expectedReqNumber: 0, + expectedUser: expectedUserDefault, + expectedDevice: expectedDeviceDefault, + expectedSource: expectedSourceDefault, + }, + { + name: "transmit_ufpd_allowed", + req: newBidRequest(), + privacyConfig: getTransmitUFPDActivityConfig("appnexus", true), + ortbVersion: "2.6", + expectedReqNumber: 1, + expectedUser: expectedUserDefault, + expectedDevice: expectedDeviceDefault, + expectedSource: expectedSourceDefault, + }, + { + //remove user.eids, user.ext.data.*, user.data.*, user.{id, buyeruid, yob, gender} + //and device-specific IDs + name: "transmit_ufpd_deny", + req: newBidRequest(), + privacyConfig: getTransmitUFPDActivityConfig("appnexus", false), + expectedReqNumber: 1, + expectedUser: openrtb2.User{ + ID: "", + BuyerUID: "", + Yob: 0, + Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.456), Lon: ptrutil.ToPtr(11.278)}, + EIDs: nil, + Ext: json.RawMessage(`{"test":2}`), + Data: nil, + }, + expectUserScrub: true, + expectedDevice: openrtb2.Device{ + UA: deviceUA, + Language: "EN", + IP: "132.173.230.74", + DIDMD5: "", + IFA: "", + DIDSHA1: "", + DPIDMD5: "", + DPIDSHA1: "", + MACMD5: "", + MACSHA1: "", + Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.456), Lon: ptrutil.ToPtr(11.278)}, + }, + expectedSource: expectedSourceDefault, + }, + { + name: "transmit_precise_geo_allowed", + req: newBidRequest(), + privacyConfig: getTransmitPreciseGeoActivityConfig("appnexus", true), + ortbVersion: "2.6", + expectedReqNumber: 1, + expectedUser: expectedUserDefault, + expectedDevice: expectedDeviceDefault, + expectedSource: expectedSourceDefault, + }, + { + //round user's geographic location by rounding off IP address and lat/lng data. + //this applies to both device.geo and user.geo + name: "transmit_precise_geo_deny", + req: newBidRequest(), + privacyConfig: getTransmitPreciseGeoActivityConfig("appnexus", false), + ortbVersion: "2.6", + expectedReqNumber: 1, + expectedUser: openrtb2.User{ + ID: "our-id", + BuyerUID: "their-id", + Yob: 1982, + Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.46), Lon: ptrutil.ToPtr(11.28)}, + Gender: "test", + Ext: json.RawMessage(`{"data": 1, "test": 2}`), + EIDs: []openrtb2.EID{ + {Source: "eids-source"}, + }, + Data: []openrtb2.Data{{ID: "data-id"}}, + }, + expectedDevice: openrtb2.Device{ + UA: deviceUA, + IP: "132.173.0.0", + Language: "EN", + DIDMD5: "DIDMD5", + IFA: "IFA", + DIDSHA1: "DIDSHA1", + DPIDMD5: "DPIDMD5", + DPIDSHA1: "DPIDSHA1", + MACMD5: "MACMD5", + MACSHA1: "MACSHA1", + Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.46), Lon: ptrutil.ToPtr(11.28)}, + }, + expectedSource: expectedSourceDefault, + }, + { + name: "transmit_tid_allowed", + req: newBidRequest(), + privacyConfig: getTransmitTIDActivityConfig("appnexus", true), + ortbVersion: "2.6", + expectedReqNumber: 1, + expectedUser: expectedUserDefault, + expectedDevice: expectedDeviceDefault, + expectedSource: expectedSourceDefault, + }, + { + //remove source.tid and imp.ext.tid + name: "transmit_tid_deny", + req: newBidRequest(), + privacyConfig: getTransmitTIDActivityConfig("appnexus", false), + ortbVersion: "2.6", + expectedReqNumber: 1, + expectedUser: expectedUserDefault, + expectedDevice: expectedDeviceDefault, + expectedSource: openrtb2.Source{ + TID: "", + }, + expectedImpExt: json.RawMessage(`{"bidder": {"placementId": 1}}`), }, } for _, test := range testCases { - activities := privacy.NewActivityControl(&test.privacyConfig) - auctionReq := AuctionRequest{ - BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: test.req}, - UserSyncs: &emptyUsersync{}, - Activities: activities, - } + t.Run(test.name, func(t *testing.T) { + activities := privacy.NewActivityControl(&test.privacyConfig) + auctionReq := AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: test.req}, + UserSyncs: &emptyUsersync{}, + Activities: activities, + Account: config.Account{Privacy: config.AccountPrivacy{ + IPv6Config: config.IPv6{ + AnonKeepBits: 32, + }, + IPv4Config: config.IPv4{ + AnonKeepBits: 16, + }, + }}, + TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), + } - bidderToSyncerKey := map[string]string{} - reqSplitter := &requestSplitter{ - bidderToSyncerKey: bidderToSyncerKey, - me: &metrics.MetricsEngineMock{}, - hostSChainNode: nil, - bidderInfo: config.BidderInfos{}, - } + metricsMock := metrics.MetricsEngineMock{} + metricsMock.Mock.On("RecordAdapterBuyerUIDScrubbed", mock.Anything).Return() - t.Run(test.name, func(t *testing.T) { - bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, map[string]float64{}) + bidderToSyncerKey := map[string]string{} + reqSplitter := &requestSplitter{ + bidderToSyncerKey: bidderToSyncerKey, + me: &metricsMock, + hostSChainNode: nil, + bidderInfo: config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{Version: test.ortbVersion}}}, + } + + bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, map[string]float64{}) assert.Empty(t, errs) assert.Len(t, bidderRequests, test.expectedReqNumber) if test.expectedReqNumber == 1 { - assert.Equal(t, test.expectedUserYOB, bidderRequests[0].BidRequest.User.Yob) - assert.Equal(t, test.expectedUserLat, bidderRequests[0].BidRequest.User.Geo.Lat) - assert.Equal(t, test.expectedDeviceDIDMD5, bidderRequests[0].BidRequest.Device.DIDMD5) - assert.Equal(t, test.expectedSourceTID, bidderRequests[0].BidRequest.Source.TID) + assert.Equal(t, &test.expectedUser, bidderRequests[0].BidRequest.User) + assert.Equal(t, &test.expectedDevice, bidderRequests[0].BidRequest.Device) + assert.Equal(t, &test.expectedSource, bidderRequests[0].BidRequest.Source) + + if len(test.expectedImpExt) > 0 { + assert.JSONEq(t, string(test.expectedImpExt), string(bidderRequests[0].BidRequest.Imp[0].Ext)) + } + if test.expectUserScrub { + metricsMock.AssertCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus) + } else { + metricsMock.AssertNotCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus) + } } }) } @@ -4638,119 +5259,503 @@ func getTransmitTIDActivityConfig(componentName string, allow bool) config.Accou func TestApplyBidAdjustmentToFloor(t *testing.T) { type args struct { - allBidderRequests []BidderRequest + bidRequestWrapper *openrtb_ext.RequestWrapper + bidderName string bidAdjustmentFactors map[string]float64 } tests := []struct { - name string - args args - expectedAllBidderRequests []BidderRequest + name string + args args + expectedBidRequest *openrtb2.BidRequest }{ { - name: " bidAdjustmentFactor is empty", + name: "bid_adjustment_factor_is_nil", args: args{ - allBidderRequests: []BidderRequest{ - { - BidRequest: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, - }, - BidderName: openrtb_ext.BidderName("appnexus"), + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, }, }, - bidAdjustmentFactors: map[string]float64{}, + bidderName: "appnexus", + bidAdjustmentFactors: nil, }, - expectedAllBidderRequests: []BidderRequest{ - { + expectedBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, + }, + }, + { + name: "bid_adjustment_factor_is_empty", + args: args{ + bidRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{ Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, }, - BidderName: openrtb_ext.BidderName("appnexus"), }, + bidderName: "appnexus", + bidAdjustmentFactors: map[string]float64{}, + }, + expectedBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, }, }, { - name: "bidAdjustmentFactor not present for request bidder", + name: "bid_adjustment_factor_not_present", args: args{ - allBidderRequests: []BidderRequest{ - { - BidRequest: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, - }, - BidderName: openrtb_ext.BidderName("appnexus"), + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, }, }, + bidderName: "appnexus", bidAdjustmentFactors: map[string]float64{"pubmatic": 1.0}, }, - expectedAllBidderRequests: []BidderRequest{ - { + expectedBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, + }, + }, + { + name: "bid_adjustment_factor_present", + args: args{ + bidRequestWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{ Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, }, - BidderName: openrtb_ext.BidderName("appnexus"), }, + bidderName: "appnexus", + bidAdjustmentFactors: map[string]float64{"pubmatic": 1.0, "appnexus": 0.75}, + }, + expectedBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 133.33333333333334}, {BidFloor: 200}}, }, }, { - name: "bidAdjustmentFactor present for request bidder", + name: "bid_adjustment_factor_present_and_zero", args: args{ - allBidderRequests: []BidderRequest{ - { - BidRequest: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, + }, + }, + bidderName: "appnexus", + bidAdjustmentFactors: map[string]float64{"pubmatic": 1.0, "appnexus": 0.0}, + }, + expectedBidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + applyBidAdjustmentToFloor(tt.args.bidRequestWrapper, tt.args.bidderName, tt.args.bidAdjustmentFactors) + assert.NoError(t, tt.args.bidRequestWrapper.RebuildRequest()) + assert.Equal(t, tt.expectedBidRequest, tt.args.bidRequestWrapper.BidRequest, tt.name) + }) + } +} + +func TestBuildRequestExtAlternateBidderCodes(t *testing.T) { + type testInput struct { + bidderNameRaw string + accABC *openrtb_ext.ExtAlternateBidderCodes + reqABC *openrtb_ext.ExtAlternateBidderCodes + } + testCases := []struct { + desc string + in testInput + expected *openrtb_ext.ExtAlternateBidderCodes + }{ + { + desc: "No biddername, nil reqABC and accABC", + in: testInput{}, + expected: nil, + }, + { + desc: "No biddername, non-nil reqABC", + in: testInput{ + reqABC: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + { + desc: "No biddername, non-nil accABC", + in: testInput{ + accABC: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + { + desc: "No biddername, non-nil reqABC nor accABC", + in: testInput{ + reqABC: &openrtb_ext.ExtAlternateBidderCodes{}, + accABC: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + { + desc: "non-nil reqABC", + in: testInput{ + bidderNameRaw: "pubmatic", + reqABC: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + { + desc: "non-nil accABC", + in: testInput{ + bidderNameRaw: "pubmatic", + accABC: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{}, + }, + { + desc: "both reqABC and accABC enabled and bidder matches elements in accABC but reqABC comes first", + in: testInput{ + bidderNameRaw: "PUBmatic", + reqABC: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "appnexus": { + AllowedBidderCodes: []string{"pubCode1"}, + }, + }, + }, + accABC: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "PubMatic": { + AllowedBidderCodes: []string{"pubCode2"}, }, - BidderName: openrtb_ext.BidderName("appnexus"), }, }, - bidAdjustmentFactors: map[string]float64{"pubmatic": 1.0, "appnexus": 0.75}, }, - expectedAllBidderRequests: []BidderRequest{ - { - BidRequest: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{BidFloor: 133.33333333333334}, {BidFloor: 200}}, + expected: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true}, + }, + { + desc: "both reqABC and accABC enabled and bidder matches elements in both but we prioritize reqABC", + in: testInput{ + bidderNameRaw: "pubmatic", + reqABC: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "PubMatic": { + AllowedBidderCodes: []string{"pubCode"}, + }, + }, + }, + accABC: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "appnexus": { + AllowedBidderCodes: []string{"anxsCode"}, + }, + }, + }, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "pubmatic": { + AllowedBidderCodes: []string{"pubCode"}, }, - BidderName: openrtb_ext.BidderName("appnexus"), }, }, }, { - name: "bidAdjustmentFactor present only for appnexus request bidder", - args: args{ - allBidderRequests: []BidderRequest{ - { - BidRequest: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, + desc: "nil reqABC non-nil accABC enabled and bidder matches elements in accABC", + in: testInput{ + bidderNameRaw: "APPnexus", + accABC: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "appnexus": { + AllowedBidderCodes: []string{"anxsCode"}, }, - BidderName: openrtb_ext.BidderName("appnexus"), }, - { - BidRequest: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, + }, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "APPnexus": { + AllowedBidderCodes: []string{"anxsCode"}, + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + alternateBidderCodes := buildRequestExtAlternateBidderCodes(tc.in.bidderNameRaw, tc.in.accABC, tc.in.reqABC) + assert.Equal(t, tc.expected, alternateBidderCodes) + }) + } +} + +func TestCopyExtAlternateBidderCodes(t *testing.T) { + type testInput struct { + bidder string + alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes + } + testCases := []struct { + desc string + in testInput + expected *openrtb_ext.ExtAlternateBidderCodes + }{ + { + desc: "pass a nil alternateBidderCodes argument, expect nil output", + in: testInput{}, + expected: nil, + }, + { + desc: "non-nil alternateBidderCodes argument but bidder doesn't match", + in: testInput{ + alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + }, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + }, + }, + { + desc: "non-nil alternateBidderCodes argument bidder is identical to one element in map", + in: testInput{ + bidder: "appnexus", + alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "appnexus": { + AllowedBidderCodes: []string{"adnxs"}, }, - BidderName: openrtb_ext.BidderName("pubmatic"), }, }, - bidAdjustmentFactors: map[string]float64{"appnexus": 0.75}, }, - expectedAllBidderRequests: []BidderRequest{ - { - BidRequest: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{BidFloor: 133.33333333333334}, {BidFloor: 200}}, + expected: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "appnexus": { + AllowedBidderCodes: []string{"adnxs"}, + }, + }, + }, + }, + { + desc: "case insensitive match, keep bidder casing in output", + in: testInput{ + bidder: "AppNexus", + alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "appnexus": { + AllowedBidderCodes: []string{"adnxs"}, + }, + }, + }, + }, + expected: &openrtb_ext.ExtAlternateBidderCodes{ + Enabled: true, + Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ + "AppNexus": { + AllowedBidderCodes: []string{"adnxs"}, + }, + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + alternateBidderCodes := copyExtAlternateBidderCodes(tc.in.bidder, tc.in.alternateBidderCodes) + assert.Equal(t, tc.expected, alternateBidderCodes) + }) + } +} + +func TestRemoveImpsWithStoredResponses(t *testing.T) { + bidRespId1 := json.RawMessage(`{"id": "resp_id1"}`) + testCases := []struct { + description string + req *openrtb_ext.RequestWrapper + storedBidResponses map[string]json.RawMessage + expectedImps []openrtb2.Imp + }{ + { + description: "request with imps and stored bid response for this imp", + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1"}, + }, + }, + }, + storedBidResponses: map[string]json.RawMessage{ + "imp-id1": bidRespId1, + }, + expectedImps: nil, + }, + { + description: "request with imps and stored bid response for one of these imp", + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1"}, + {ID: "imp-id2"}, }, - BidderName: openrtb_ext.BidderName("appnexus"), }, + }, + storedBidResponses: map[string]json.RawMessage{ + "imp-id1": bidRespId1, + }, + expectedImps: []openrtb2.Imp{ { - BidRequest: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, + ID: "imp-id2", + }, + }, + }, + { + description: "request with imps and stored bid response for both of these imp", + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1"}, + {ID: "imp-id2"}, }, - BidderName: openrtb_ext.BidderName("pubmatic"), }, }, + storedBidResponses: map[string]json.RawMessage{ + "imp-id1": bidRespId1, + "imp-id2": bidRespId1, + }, + expectedImps: nil, + }, + { + description: "request with imps and no stored bid responses", + req: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1"}, + {ID: "imp-id2"}, + }, + }, + }, + storedBidResponses: nil, + + expectedImps: []openrtb2.Imp{ + {ID: "imp-id1"}, + {ID: "imp-id2"}, + }, }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - applyBidAdjustmentToFloor(tt.args.allBidderRequests, tt.args.bidAdjustmentFactors) - assert.Equal(t, tt.expectedAllBidderRequests, tt.args.allBidderRequests, tt.name) + for _, testCase := range testCases { + request := testCase.req + removeImpsWithStoredResponses(request, testCase.storedBidResponses) + assert.NoError(t, request.RebuildRequest()) + assert.Equal(t, testCase.expectedImps, request.Imp, "incorrect Impressions for testCase %s", testCase.description) + } +} + +func TestExtractAndCleanBuyerUIDs(t *testing.T) { + tests := []struct { + name string + user *openrtb2.User + expectedBuyerUIDs map[string]string + expectedUser *openrtb2.User + expectError bool + }{ + { + name: "user_is_nil", + user: nil, + expectedBuyerUIDs: nil, + expectedUser: nil, + expectError: false, + }, + { + name: "user.ext_is_nil", + user: &openrtb2.User{ + Ext: nil, + }, + expectedBuyerUIDs: nil, + expectedUser: &openrtb2.User{ + Ext: nil, + }, + expectError: false, + }, + { + name: "user.ext_malformed", + user: &openrtb2.User{ + Ext: json.RawMessage(`{"prebid":}`), + }, + expectedBuyerUIDs: nil, + expectedUser: &openrtb2.User{ + Ext: json.RawMessage(`{"prebid":}`), + }, + expectError: true, + }, + { + name: "user.ext.prebid_is_nil", + user: &openrtb2.User{ + Ext: json.RawMessage(`{"prebid":null}`), + }, + expectedBuyerUIDs: nil, + expectedUser: &openrtb2.User{ + Ext: nil, + }, + expectError: false, + }, + { + name: "user.ext.prebid.buyeruids_is_nil", + user: &openrtb2.User{ + Ext: json.RawMessage(`{"prebid":{"buyeruids": null}}`), + }, + expectedBuyerUIDs: nil, + expectedUser: &openrtb2.User{ + Ext: nil, + }, + expectError: false, + }, + { + name: "user.ext.prebid.buyeruids_has_one", + user: &openrtb2.User{ + Ext: json.RawMessage(`{"prebid":{"buyeruids": {"appnexus":"a"}}}`), + }, + expectedBuyerUIDs: map[string]string{"appnexus": "a"}, + expectedUser: &openrtb2.User{ + Ext: nil, + }, + expectError: false, + }, + { + name: "user.ext.prebid.buyeruids_has_many", + user: &openrtb2.User{ + Ext: json.RawMessage(`{"prebid":{"buyeruids": {"appnexus":"a", "pubmatic":"b"}}}`), + }, + expectedBuyerUIDs: map[string]string{"appnexus": "a", "pubmatic": "b"}, + expectedUser: &openrtb2.User{ + Ext: nil, + }, + expectError: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + req := openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + User: test.user, + }, + } + + result, err := extractAndCleanBuyerUIDs(&req) + if test.expectError { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + } + + assert.NoError(t, req.RebuildRequest()) + + assert.Equal(t, req.User, test.expectedUser) + assert.Equal(t, test.expectedBuyerUIDs, result) }) } } diff --git a/experiment/adscert/inprocesssigner.go b/experiment/adscert/inprocesssigner.go index 604287f9ed6..ee5f4905d2a 100644 --- a/experiment/adscert/inprocesssigner.go +++ b/experiment/adscert/inprocesssigner.go @@ -2,12 +2,13 @@ package adscert import ( "crypto/rand" + "time" + "github.com/IABTechLab/adscert/pkg/adscert/api" "github.com/IABTechLab/adscert/pkg/adscert/discovery" "github.com/IABTechLab/adscert/pkg/adscert/signatory" "github.com/benbjohnson/clock" - "github.com/prebid/prebid-server/config" - "time" + "github.com/prebid/prebid-server/v3/config" ) // inProcessSigner holds the signatory to add adsCert header to requests using in process go library diff --git a/experiment/adscert/remotesigner.go b/experiment/adscert/remotesigner.go index 3c9479560b2..c441bbebb63 100644 --- a/experiment/adscert/remotesigner.go +++ b/experiment/adscert/remotesigner.go @@ -2,12 +2,13 @@ package adscert import ( "fmt" + "time" + "github.com/IABTechLab/adscert/pkg/adscert/api" "github.com/IABTechLab/adscert/pkg/adscert/signatory" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v3/config" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - "time" ) // remoteSigner holds the signatory to add adsCert header to requests using remote signing server diff --git a/experiment/adscert/signer.go b/experiment/adscert/signer.go index 08b3f655fa2..f7ea9a4254d 100644 --- a/experiment/adscert/signer.go +++ b/experiment/adscert/signer.go @@ -2,10 +2,11 @@ package adscert import ( "fmt" + "github.com/IABTechLab/adscert/pkg/adscert/api" "github.com/IABTechLab/adscert/pkg/adscert/logger" "github.com/IABTechLab/adscert/pkg/adscert/signatory" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v3/config" ) const SignHeader = "X-Ads-Cert-Auth" diff --git a/experiment/adscert/signer_test.go b/experiment/adscert/signer_test.go index d6d02175d95..3e08d0f6ba0 100644 --- a/experiment/adscert/signer_test.go +++ b/experiment/adscert/signer_test.go @@ -2,10 +2,11 @@ package adscert import ( "errors" + "testing" + "github.com/IABTechLab/adscert/pkg/adscert/api" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v3/config" "github.com/stretchr/testify/assert" - "testing" ) func TestNilSigner(t *testing.T) { diff --git a/firstpartydata/extmerger.go b/firstpartydata/extmerger.go deleted file mode 100644 index 119fa8a4c3c..00000000000 --- a/firstpartydata/extmerger.go +++ /dev/null @@ -1,60 +0,0 @@ -package firstpartydata - -import ( - "encoding/json" - "errors" - "fmt" - - "github.com/prebid/prebid-server/util/sliceutil" - jsonpatch "gopkg.in/evanphx/json-patch.v4" -) - -var ( - ErrBadRequest = fmt.Errorf("invalid request ext") - ErrBadFPD = fmt.Errorf("invalid first party data ext") -) - -// extMerger tracks a JSON `ext` field within an OpenRTB request. The value of the -// `ext` field is expected to be modified when calling unmarshal on the same object -// and will later be updated when invoking Merge. -type extMerger struct { - ext *json.RawMessage // Pointer to the JSON `ext` field. - snapshot json.RawMessage // Copy of the original state of the JSON `ext` field. -} - -// Track saves a copy of the JSON `ext` field and stores a reference to the extension -// object for comparison later in the Merge call. -func (e *extMerger) Track(ext *json.RawMessage) { - e.ext = ext - e.snapshot = sliceutil.Clone(*ext) -} - -// Merge applies a JSON merge of the stored extension snapshot on top of the current -// JSON of the tracked extension object. -func (e extMerger) Merge() error { - if e.ext == nil { - return nil - } - - if len(e.snapshot) == 0 { - return nil - } - - if len(*e.ext) == 0 { - *e.ext = e.snapshot - return nil - } - - merged, err := jsonpatch.MergePatch(e.snapshot, *e.ext) - if err != nil { - if errors.Is(err, jsonpatch.ErrBadJSONDoc) { - return ErrBadRequest - } else if errors.Is(err, jsonpatch.ErrBadJSONPatch) { - return ErrBadFPD - } - return err - } - - *e.ext = merged - return nil -} diff --git a/firstpartydata/extmerger_test.go b/firstpartydata/extmerger_test.go deleted file mode 100644 index 784163ac313..00000000000 --- a/firstpartydata/extmerger_test.go +++ /dev/null @@ -1,109 +0,0 @@ -package firstpartydata - -import ( - "encoding/json" - "testing" - - "github.com/prebid/prebid-server/util/sliceutil" - "github.com/stretchr/testify/assert" -) - -func TestExtMerger(t *testing.T) { - t.Run("nil", func(t *testing.T) { - merger := extMerger{ext: nil, snapshot: json.RawMessage(`{"a":1}`)} - assert.NoError(t, merger.Merge()) - assert.Nil(t, merger.ext) - }) - - testCases := []struct { - name string - givenOriginal json.RawMessage - givenFPD json.RawMessage - expectedExt json.RawMessage - expectedErr string - }{ - { - name: "both-populated", - givenOriginal: json.RawMessage(`{"a":1,"b":2}`), - givenFPD: json.RawMessage(`{"b":200,"c":3}`), - expectedExt: json.RawMessage(`{"a":1,"b":200,"c":3}`), - }, - { - name: "both-nil", - givenFPD: nil, - givenOriginal: nil, - expectedExt: nil, - }, - { - name: "both-empty", - givenOriginal: json.RawMessage(`{}`), - givenFPD: json.RawMessage(`{}`), - expectedExt: json.RawMessage(`{}`), - }, - { - name: "ext-nil", - givenOriginal: json.RawMessage(`{"b":2}`), - givenFPD: nil, - expectedExt: json.RawMessage(`{"b":2}`), - }, - { - name: "ext-empty", - givenOriginal: json.RawMessage(`{"b":2}`), - givenFPD: json.RawMessage(`{}`), - expectedExt: json.RawMessage(`{"b":2}`), - }, - { - name: "ext-malformed", - givenOriginal: json.RawMessage(`{"b":2}`), - givenFPD: json.RawMessage(`malformed`), - expectedErr: "invalid first party data ext", - }, - { - name: "snapshot-nil", - givenOriginal: nil, - givenFPD: json.RawMessage(`{"a":1}`), - expectedExt: json.RawMessage(`{"a":1}`), - }, - { - name: "snapshot-empty", - givenOriginal: json.RawMessage(`{}`), - givenFPD: json.RawMessage(`{"a":1}`), - expectedExt: json.RawMessage(`{"a":1}`), - }, - { - name: "snapshot-malformed", - givenOriginal: json.RawMessage(`malformed`), - givenFPD: json.RawMessage(`{"a":1}`), - expectedErr: "invalid request ext", - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - // Initialize A Ext Raw Message For Testing - simulatedExt := json.RawMessage(sliceutil.Clone(test.givenOriginal)) - - // Begin Tracking - var merger extMerger - merger.Track(&simulatedExt) - - // Unmarshal - simulatedExt.UnmarshalJSON(test.givenFPD) - - // Merge - actualErr := merger.Merge() - - if test.expectedErr == "" { - assert.NoError(t, actualErr, "error") - - if test.expectedExt == nil { - assert.Nil(t, simulatedExt, "json") - } else { - assert.JSONEq(t, string(test.expectedExt), string(simulatedExt), "json") - } - } else { - assert.EqualError(t, actualErr, test.expectedErr, "error") - } - }) - } -} diff --git a/firstpartydata/first_party_data.go b/firstpartydata/first_party_data.go index b23ba576fcf..44a3241a53c 100644 --- a/firstpartydata/first_party_data.go +++ b/firstpartydata/first_party_data.go @@ -2,16 +2,22 @@ package firstpartydata import ( "encoding/json" + "errors" "fmt" + "strings" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" jsonpatch "gopkg.in/evanphx/json-patch.v4" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/ortb" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" +) + +var ( + ErrBadRequest = errors.New("invalid request ext") + ErrBadFPD = errors.New("invalid first party data ext") ) const ( @@ -185,7 +191,7 @@ func resolveUser(fpdConfig *openrtb_ext.ORTB2, bidRequestUser *openrtb2.User, gl var err error newUser.Ext, err = jsonpatch.MergePatch(newUser.Ext, extData) if err != nil { - return nil, err + return nil, formatMergePatchError(err) } } else { newUser.Ext = extData @@ -195,42 +201,14 @@ func resolveUser(fpdConfig *openrtb_ext.ORTB2, bidRequestUser *openrtb2.User, gl newUser.Data = openRtbGlobalFPD[userDataKey] } if fpdConfigUser != nil { - if err := mergeUser(newUser, fpdConfigUser); err != nil { - return nil, err + if err := jsonutil.MergeClone(newUser, fpdConfigUser); err != nil { + return nil, formatMergeCloneError(err) } } return newUser, nil } -func mergeUser(v *openrtb2.User, overrideJSON json.RawMessage) error { - *v = *ortb.CloneUser(v) - - // Track EXTs - // It's not necessary to track `ext` fields in array items because the array - // items will be replaced entirely with the override JSON, so no merge is required. - var ext, extGeo extMerger - ext.Track(&v.Ext) - if v.Geo != nil { - extGeo.Track(&v.Geo.Ext) - } - - // Merge - if err := jsonutil.Unmarshal(overrideJSON, &v); err != nil { - return err - } - - // Merge EXTs - if err := ext.Merge(); err != nil { - return err - } - if err := extGeo.Merge(); err != nil { - return err - } - - return nil -} - func resolveSite(fpdConfig *openrtb_ext.ORTB2, bidRequestSite *openrtb2.Site, globalFPD map[string][]byte, openRtbGlobalFPD map[string][]openrtb2.Data, bidderName string) (*openrtb2.Site, error) { var fpdConfigSite json.RawMessage @@ -261,7 +239,7 @@ func resolveSite(fpdConfig *openrtb_ext.ORTB2, bidRequestSite *openrtb2.Site, gl var err error newSite.Ext, err = jsonpatch.MergePatch(newSite.Ext, extData) if err != nil { - return nil, err + return nil, formatMergePatchError(err) } } else { newSite.Ext = extData @@ -278,70 +256,37 @@ func resolveSite(fpdConfig *openrtb_ext.ORTB2, bidRequestSite *openrtb2.Site, gl newSite.Content.Data = openRtbGlobalFPD[siteContentDataKey] } if fpdConfigSite != nil { - if err := mergeSite(newSite, fpdConfigSite, bidderName); err != nil { - return nil, err + if err := jsonutil.MergeClone(newSite, fpdConfigSite); err != nil { + return nil, formatMergeCloneError(err) + } + + // Re-Validate Site + if newSite.ID == "" && newSite.Page == "" { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("incorrect First Party Data for bidder %s: Site object cannot set empty page if req.site.id is empty", bidderName), + } } } return newSite, nil } -func mergeSite(v *openrtb2.Site, overrideJSON json.RawMessage, bidderName string) error { - *v = *ortb.CloneSite(v) - - // Track EXTs - // It's not necessary to track `ext` fields in array items because the array - // items will be replaced entirely with the override JSON, so no merge is required. - var ext, extPublisher, extContent, extContentProducer, extContentNetwork, extContentChannel extMerger - ext.Track(&v.Ext) - if v.Publisher != nil { - extPublisher.Track(&v.Publisher.Ext) - } - if v.Content != nil { - extContent.Track(&v.Content.Ext) - } - if v.Content != nil && v.Content.Producer != nil { - extContentProducer.Track(&v.Content.Producer.Ext) - } - if v.Content != nil && v.Content.Network != nil { - extContentNetwork.Track(&v.Content.Network.Ext) - } - if v.Content != nil && v.Content.Channel != nil { - extContentChannel.Track(&v.Content.Channel.Ext) +func formatMergePatchError(err error) error { + if errors.Is(err, jsonpatch.ErrBadJSONDoc) { + return ErrBadRequest } - // Merge - if err := jsonutil.Unmarshal(overrideJSON, &v); err != nil { - return err + if errors.Is(err, jsonpatch.ErrBadJSONPatch) { + return ErrBadFPD } - // Merge EXTs - if err := ext.Merge(); err != nil { - return err - } - if err := extPublisher.Merge(); err != nil { - return err - } - if err := extContent.Merge(); err != nil { - return err - } - if err := extContentProducer.Merge(); err != nil { - return err - } - if err := extContentNetwork.Merge(); err != nil { - return err - } - if err := extContentChannel.Merge(); err != nil { - return err - } + return err +} - // Re-Validate Site - if v.ID == "" && v.Page == "" { - return &errortypes.BadInput{ - Message: fmt.Sprintf("incorrect First Party Data for bidder %s: Site object cannot set empty page if req.site.id is empty", bidderName), - } +func formatMergeCloneError(err error) error { + if strings.Contains(err.Error(), "invalid json on existing object") { + return ErrBadRequest } - - return nil + return ErrBadFPD } func resolveApp(fpdConfig *openrtb_ext.ORTB2, bidRequestApp *openrtb2.App, globalFPD map[string][]byte, openRtbGlobalFPD map[string][]openrtb2.Data, bidderName string) (*openrtb2.App, error) { @@ -375,7 +320,7 @@ func resolveApp(fpdConfig *openrtb_ext.ORTB2, bidRequestApp *openrtb2.App, globa var err error newApp.Ext, err = jsonpatch.MergePatch(newApp.Ext, extData) if err != nil { - return nil, err + return nil, formatMergePatchError(err) } } else { newApp.Ext = extData @@ -394,66 +339,14 @@ func resolveApp(fpdConfig *openrtb_ext.ORTB2, bidRequestApp *openrtb2.App, globa } if fpdConfigApp != nil { - if err := mergeApp(newApp, fpdConfigApp); err != nil { - return nil, err + if err := jsonutil.MergeClone(newApp, fpdConfigApp); err != nil { + return nil, formatMergeCloneError(err) } } return newApp, nil } -func mergeApp(v *openrtb2.App, overrideJSON json.RawMessage) error { - *v = *ortb.CloneApp(v) - - // Track EXTs - // It's not necessary to track `ext` fields in array items because the array - // items will be replaced entirely with the override JSON, so no merge is required. - var ext, extPublisher, extContent, extContentProducer, extContentNetwork, extContentChannel extMerger - ext.Track(&v.Ext) - if v.Publisher != nil { - extPublisher.Track(&v.Publisher.Ext) - } - if v.Content != nil { - extContent.Track(&v.Content.Ext) - } - if v.Content != nil && v.Content.Producer != nil { - extContentProducer.Track(&v.Content.Producer.Ext) - } - if v.Content != nil && v.Content.Network != nil { - extContentNetwork.Track(&v.Content.Network.Ext) - } - if v.Content != nil && v.Content.Channel != nil { - extContentChannel.Track(&v.Content.Channel.Ext) - } - - // Merge - if err := jsonutil.Unmarshal(overrideJSON, &v); err != nil { - return err - } - - // Merge EXTs - if err := ext.Merge(); err != nil { - return err - } - if err := extPublisher.Merge(); err != nil { - return err - } - if err := extContent.Merge(); err != nil { - return err - } - if err := extContentProducer.Merge(); err != nil { - return err - } - if err := extContentNetwork.Merge(); err != nil { - return err - } - if err := extContentChannel.Merge(); err != nil { - return err - } - - return nil -} - func buildExtData(data []byte) []byte { res := make([]byte, 0, len(data)+len(`"{"data":}"`)) res = append(res, []byte(`{"data":`)...) diff --git a/firstpartydata/first_party_data_test.go b/firstpartydata/first_party_data_test.go index 61369738bf3..3a11e44478b 100644 --- a/firstpartydata/first_party_data_test.go +++ b/firstpartydata/first_party_data_test.go @@ -4,13 +4,12 @@ import ( "encoding/json" "os" "path/filepath" - "reflect" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -33,17 +32,17 @@ func TestExtractGlobalFPD(t *testing.T) { Publisher: &openrtb2.Publisher{ ID: "1", }, - Ext: json.RawMessage(`{"data": {"somesitefpd": "sitefpdDataTest"}}`), + Ext: json.RawMessage(`{"data":{"somesitefpd":"sitefpdDataTest"}}`), }, User: &openrtb2.User{ ID: "reqUserID", Yob: 1982, Gender: "M", - Ext: json.RawMessage(`{"data": {"someuserfpd": "userfpdDataTest"}}`), + Ext: json.RawMessage(`{"data":{"someuserfpd":"userfpdDataTest"}}`), }, App: &openrtb2.App{ ID: "appId", - Ext: json.RawMessage(`{"data": {"someappfpd": "appfpdDataTest"}}`), + Ext: json.RawMessage(`{"data":{"someappfpd":"appfpdDataTest"}}`), }, }, }, @@ -66,9 +65,9 @@ func TestExtractGlobalFPD(t *testing.T) { }, }}, expectedFpd: map[string][]byte{ - "site": []byte(`{"somesitefpd": "sitefpdDataTest"}`), - "user": []byte(`{"someuserfpd": "userfpdDataTest"}`), - "app": []byte(`{"someappfpd": "appfpdDataTest"}`), + "site": []byte(`{"somesitefpd":"sitefpdDataTest"}`), + "user": []byte(`{"someuserfpd":"userfpdDataTest"}`), + "app": []byte(`{"someappfpd":"appfpdDataTest"}`), }, }, { @@ -85,7 +84,7 @@ func TestExtractGlobalFPD(t *testing.T) { }, App: &openrtb2.App{ ID: "appId", - Ext: json.RawMessage(`{"data": {"someappfpd": "appfpdDataTest"}}`), + Ext: json.RawMessage(`{"data":{"someappfpd":"appfpdDataTest"}}`), }, }, }, @@ -105,7 +104,7 @@ func TestExtractGlobalFPD(t *testing.T) { }, }, expectedFpd: map[string][]byte{ - "app": []byte(`{"someappfpd": "appfpdDataTest"}`), + "app": []byte(`{"someappfpd":"appfpdDataTest"}`), "user": nil, "site": nil, }, @@ -126,7 +125,7 @@ func TestExtractGlobalFPD(t *testing.T) { ID: "reqUserID", Yob: 1982, Gender: "M", - Ext: json.RawMessage(`{"data": {"someuserfpd": "userfpdDataTest"}}`), + Ext: json.RawMessage(`{"data":{"someuserfpd":"userfpdDataTest"}}`), }, }, }, @@ -149,7 +148,7 @@ func TestExtractGlobalFPD(t *testing.T) { }, expectedFpd: map[string][]byte{ "app": nil, - "user": []byte(`{"someuserfpd": "userfpdDataTest"}`), + "user": []byte(`{"someuserfpd":"userfpdDataTest"}`), "site": nil, }, }, @@ -212,7 +211,7 @@ func TestExtractGlobalFPD(t *testing.T) { Publisher: &openrtb2.Publisher{ ID: "1", }, - Ext: json.RawMessage(`{"data": {"someappfpd": true}}`), + Ext: json.RawMessage(`{"data":{"someappfpd":true}}`), }, App: &openrtb2.App{ ID: "appId", @@ -237,7 +236,7 @@ func TestExtractGlobalFPD(t *testing.T) { expectedFpd: map[string][]byte{ "app": nil, "user": nil, - "site": []byte(`{"someappfpd": true}`), + "site": []byte(`{"someappfpd":true}`), }, }, } @@ -524,6 +523,7 @@ func TestExtractBidderConfigFPD(t *testing.T) { }) } } + func TestResolveFPD(t *testing.T) { testPath := "tests/resolvefpd" @@ -632,6 +632,7 @@ func TestResolveFPD(t *testing.T) { }) } } + func TestExtractFPDForBidders(t *testing.T) { if specFiles, err := os.ReadDir("./tests/extractfpdforbidders"); err == nil { for _, specFile := range specFiles { @@ -726,7 +727,7 @@ func TestResolveUser(t *testing.T) { globalFPD map[string][]byte openRtbGlobalFPD map[string][]openrtb2.Data expectedUser *openrtb2.User - expectError bool + expectError string }{ { description: "FPD config and bid request user are not specified", @@ -734,42 +735,42 @@ func TestResolveUser(t *testing.T) { }, { description: "FPD config user only is specified", - fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id": "test"}`)}, + fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id":"test"}`)}, expectedUser: &openrtb2.User{ID: "test"}, }, { description: "FPD config and bid request user are specified", - fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id": "test1"}`)}, + fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id":"test1"}`)}, bidRequestUser: &openrtb2.User{ID: "test2"}, expectedUser: &openrtb2.User{ID: "test1"}, }, { description: "FPD config, bid request and global fpd user are specified, no input user ext", - fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id": "test1"}`)}, + fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id":"test1"}`)}, bidRequestUser: &openrtb2.User{ID: "test2"}, - globalFPD: map[string][]byte{userKey: []byte(`{"globalFPDUserData": "globalFPDUserDataValue"}`)}, + globalFPD: map[string][]byte{userKey: []byte(`{"globalFPDUserData":"globalFPDUserDataValue"}`)}, expectedUser: &openrtb2.User{ID: "test1", Ext: json.RawMessage(`{"data":{"globalFPDUserData":"globalFPDUserDataValue"}}`)}, }, { description: "FPD config, bid request user with ext and global fpd user are specified, no input user ext", - fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id": "test1"}`)}, + fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id":"test1"}`)}, bidRequestUser: &openrtb2.User{ID: "test2", Ext: json.RawMessage(`{"test":{"inputFPDUserData":"inputFPDUserDataValue"}}`)}, - globalFPD: map[string][]byte{userKey: []byte(`{"globalFPDUserData": "globalFPDUserDataValue"}`)}, + globalFPD: map[string][]byte{userKey: []byte(`{"globalFPDUserData":"globalFPDUserDataValue"}`)}, expectedUser: &openrtb2.User{ID: "test1", Ext: json.RawMessage(`{"data":{"globalFPDUserData":"globalFPDUserDataValue"},"test":{"inputFPDUserData":"inputFPDUserDataValue"}}`)}, }, { description: "FPD config, bid request and global fpd user are specified, with input user ext.data", fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id": "test1"}`)}, bidRequestUser: &openrtb2.User{ID: "test2", Ext: json.RawMessage(`{"data":{"inputFPDUserData":"inputFPDUserDataValue"}}`)}, - globalFPD: map[string][]byte{userKey: []byte(`{"globalFPDUserData": "globalFPDUserDataValue"}`)}, + globalFPD: map[string][]byte{userKey: []byte(`{"globalFPDUserData":"globalFPDUserDataValue"}`)}, expectedUser: &openrtb2.User{ID: "test1", Ext: json.RawMessage(`{"data":{"globalFPDUserData":"globalFPDUserDataValue","inputFPDUserData":"inputFPDUserDataValue"}}`)}, }, { description: "FPD config, bid request and global fpd user are specified, with input user ext.data malformed", - fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id": "test1"}`)}, + fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id":"test1"}`)}, bidRequestUser: &openrtb2.User{ID: "test2", Ext: json.RawMessage(`{"data":{"inputFPDUserData":"inputFPDUserDataValue"}}`)}, globalFPD: map[string][]byte{userKey: []byte(`malformed`)}, - expectError: true, + expectError: "invalid first party data ext", }, { description: "bid request and openrtb global fpd user are specified, no input user ext", @@ -785,7 +786,7 @@ func TestResolveUser(t *testing.T) { }, { description: "fpd config user, bid request and openrtb global fpd user are specified, no input user ext", - fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id": "test1"}`)}, + fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id":"test1"}`)}, bidRequestUser: &openrtb2.User{ID: "test2"}, openRtbGlobalFPD: map[string][]openrtb2.Data{userDataKey: { {ID: "DataId1", Name: "Name1"}, @@ -798,7 +799,7 @@ func TestResolveUser(t *testing.T) { }, { description: "fpd config user with ext, bid request and openrtb global fpd user are specified, no input user ext", - fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id": "test1", "ext":{"test":1}}`)}, + fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id":"test1","ext":{"test":1}}`)}, bidRequestUser: &openrtb2.User{ID: "test2"}, openRtbGlobalFPD: map[string][]openrtb2.Data{userDataKey: { {ID: "DataId1", Name: "Name1"}, @@ -812,8 +813,8 @@ func TestResolveUser(t *testing.T) { }, { description: "fpd config user with ext, bid requestuser with ext and openrtb global fpd user are specified, no input user ext", - fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id": "test1", "ext":{"test":1}}`)}, - bidRequestUser: &openrtb2.User{ID: "test2", Ext: json.RawMessage(`{"test":2, "key": "value"}`)}, + fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id":"test1","ext":{"test":1}}`)}, + bidRequestUser: &openrtb2.User{ID: "test2", Ext: json.RawMessage(`{"test":2,"key":"value"}`)}, openRtbGlobalFPD: map[string][]openrtb2.Data{userDataKey: { {ID: "DataId1", Name: "Name1"}, {ID: "DataId2", Name: "Name2"}, @@ -826,8 +827,8 @@ func TestResolveUser(t *testing.T) { }, { description: "fpd config user with malformed ext, bid requestuser with ext and openrtb global fpd user are specified, no input user ext", - fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id": "test1", "ext":{malformed}}`)}, - bidRequestUser: &openrtb2.User{ID: "test2", Ext: json.RawMessage(`{"test":2, "key": "value"}`)}, + fpdConfig: &openrtb_ext.ORTB2{User: json.RawMessage(`{"id": "test1","ext":{malformed}}`)}, + bidRequestUser: &openrtb2.User{ID: "test2", Ext: json.RawMessage(`{"test":2,"key":"value"}`)}, openRtbGlobalFPD: map[string][]openrtb2.Data{userDataKey: { {ID: "DataId1", Name: "Name1"}, {ID: "DataId2", Name: "Name2"}, @@ -838,15 +839,15 @@ func TestResolveUser(t *testing.T) { }, Ext: json.RawMessage(`{"key":"value","test":1}`), }, - expectError: true, + expectError: "invalid first party data ext", }, } for _, test := range testCases { t.Run(test.description, func(t *testing.T) { resultUser, err := resolveUser(test.fpdConfig, test.bidRequestUser, test.globalFPD, test.openRtbGlobalFPD, "bidderA") - if test.expectError { - assert.Error(t, err, "expected error incorrect") + if len(test.expectError) > 0 { + assert.EqualError(t, err, test.expectError) } else { assert.NoError(t, err, "unexpected error returned") assert.Equal(t, test.expectedUser, resultUser, "Result user is incorrect") @@ -863,7 +864,7 @@ func TestResolveSite(t *testing.T) { globalFPD map[string][]byte openRtbGlobalFPD map[string][]openrtb2.Data expectedSite *openrtb2.Site - expectError bool + expectError string }{ { description: "FPD config and bid request site are not specified", @@ -871,42 +872,42 @@ func TestResolveSite(t *testing.T) { }, { description: "FPD config site only is specified", - fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id": "test"}`)}, - expectError: true, + fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test"}`)}, + expectError: "incorrect First Party Data for bidder bidderA: Site object is not defined in request, but defined in FPD config", }, { description: "FPD config and bid request site are specified", - fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id": "test1"}`)}, + fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test1"}`)}, bidRequestSite: &openrtb2.Site{ID: "test2"}, expectedSite: &openrtb2.Site{ID: "test1"}, }, { description: "FPD config, bid request and global fpd site are specified, no input site ext", - fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id": "test1"}`)}, + fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test1"}`)}, bidRequestSite: &openrtb2.Site{ID: "test2"}, - globalFPD: map[string][]byte{siteKey: []byte(`{"globalFPDSiteData": "globalFPDSiteDataValue"}`)}, + globalFPD: map[string][]byte{siteKey: []byte(`{"globalFPDSiteData":"globalFPDSiteDataValue"}`)}, expectedSite: &openrtb2.Site{ID: "test1", Ext: json.RawMessage(`{"data":{"globalFPDSiteData":"globalFPDSiteDataValue"}}`)}, }, { description: "FPD config, bid request site with ext and global fpd site are specified, no input site ext", - fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id": "test1"}`)}, + fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test1"}`)}, bidRequestSite: &openrtb2.Site{ID: "test2", Ext: json.RawMessage(`{"test":{"inputFPDSiteData":"inputFPDSiteDataValue"}}`)}, - globalFPD: map[string][]byte{siteKey: []byte(`{"globalFPDSiteData": "globalFPDSiteDataValue"}`)}, + globalFPD: map[string][]byte{siteKey: []byte(`{"globalFPDSiteData":"globalFPDSiteDataValue"}`)}, expectedSite: &openrtb2.Site{ID: "test1", Ext: json.RawMessage(`{"data":{"globalFPDSiteData":"globalFPDSiteDataValue"},"test":{"inputFPDSiteData":"inputFPDSiteDataValue"}}`)}, }, { description: "FPD config, bid request and global fpd site are specified, with input site ext.data", - fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id": "test1"}`)}, + fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test1"}`)}, bidRequestSite: &openrtb2.Site{ID: "test2", Ext: json.RawMessage(`{"data":{"inputFPDSiteData":"inputFPDSiteDataValue"}}`)}, - globalFPD: map[string][]byte{siteKey: []byte(`{"globalFPDSiteData": "globalFPDSiteDataValue"}`)}, + globalFPD: map[string][]byte{siteKey: []byte(`{"globalFPDSiteData":"globalFPDSiteDataValue"}`)}, expectedSite: &openrtb2.Site{ID: "test1", Ext: json.RawMessage(`{"data":{"globalFPDSiteData":"globalFPDSiteDataValue","inputFPDSiteData":"inputFPDSiteDataValue"}}`)}, }, { description: "FPD config, bid request and global fpd site are specified, with input site ext.data malformed", - fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id": "test1"}`)}, + fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test1"}`)}, bidRequestSite: &openrtb2.Site{ID: "test2", Ext: json.RawMessage(`{"data":{"inputFPDSiteData":"inputFPDSiteDataValue"}}`)}, globalFPD: map[string][]byte{siteKey: []byte(`malformed`)}, - expectError: true, + expectError: "invalid first party data ext", }, { description: "bid request and openrtb global fpd site are specified, no input site ext", @@ -945,7 +946,7 @@ func TestResolveSite(t *testing.T) { }, { description: "fpd config site, bid request and openrtb global fpd site are specified, no input site ext", - fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id": "test1"}`)}, + fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test1"}`)}, bidRequestSite: &openrtb2.Site{ID: "test2"}, openRtbGlobalFPD: map[string][]openrtb2.Data{siteContentDataKey: { {ID: "DataId1", Name: "Name1"}, @@ -958,7 +959,7 @@ func TestResolveSite(t *testing.T) { }, { description: "fpd config site with ext, bid request and openrtb global fpd site are specified, no input site ext", - fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id": "test1", "ext":{"test":1}}`)}, + fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test1","ext":{"test":1}}`)}, bidRequestSite: &openrtb2.Site{ID: "test2"}, openRtbGlobalFPD: map[string][]openrtb2.Data{siteContentDataKey: { {ID: "DataId1", Name: "Name1"}, @@ -972,8 +973,8 @@ func TestResolveSite(t *testing.T) { }, { description: "fpd config site with ext, bid request site with ext and openrtb global fpd site are specified, no input site ext", - fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id": "test1", "ext":{"test":1}}`)}, - bidRequestSite: &openrtb2.Site{ID: "test2", Ext: json.RawMessage(`{"test":2, "key": "value"}`)}, + fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test1","ext":{"test":1}}`)}, + bidRequestSite: &openrtb2.Site{ID: "test2", Ext: json.RawMessage(`{"test":2,"key":"value"}`)}, openRtbGlobalFPD: map[string][]openrtb2.Data{siteContentDataKey: { {ID: "DataId1", Name: "Name1"}, {ID: "DataId2", Name: "Name2"}, @@ -986,8 +987,8 @@ func TestResolveSite(t *testing.T) { }, { description: "fpd config site with malformed ext, bid request site with ext and openrtb global fpd site are specified, no input site ext", - fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id": "test1", "ext":{malformed}}`)}, - bidRequestSite: &openrtb2.Site{ID: "test2", Ext: json.RawMessage(`{"test":2, "key": "value"}`)}, + fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test1","ext":{malformed}}`)}, + bidRequestSite: &openrtb2.Site{ID: "test2", Ext: json.RawMessage(`{"test":2,"key":"value"}`)}, openRtbGlobalFPD: map[string][]openrtb2.Data{siteContentDataKey: { {ID: "DataId1", Name: "Name1"}, {ID: "DataId2", Name: "Name2"}, @@ -998,17 +999,53 @@ func TestResolveSite(t *testing.T) { }}, Ext: json.RawMessage(`{"key":"value","test":1}`), }, - expectError: true, + expectError: "invalid first party data ext", + }, + { + description: "valid-id", + bidRequestSite: &openrtb2.Site{ID: "1"}, + fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"2"}`)}, + expectedSite: &openrtb2.Site{ID: "2"}, + }, + { + description: "valid-page", + bidRequestSite: &openrtb2.Site{Page: "1"}, + fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"page":"2"}`)}, + expectedSite: &openrtb2.Site{Page: "2"}, + }, + { + description: "invalid-id", + bidRequestSite: &openrtb2.Site{ID: "1"}, + fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":null}`)}, + expectError: "incorrect First Party Data for bidder bidderA: Site object cannot set empty page if req.site.id is empty", + }, + { + description: "invalid-page", + bidRequestSite: &openrtb2.Site{Page: "1"}, + fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"page":null}`)}, + expectError: "incorrect First Party Data for bidder bidderA: Site object cannot set empty page if req.site.id is empty", + }, + { + description: "existing-err", + bidRequestSite: &openrtb2.Site{ID: "1", Ext: []byte(`malformed`)}, + fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`{"ext":{"a":1}}`)}, + expectError: "invalid request ext", + }, + { + description: "fpd-err", + bidRequestSite: &openrtb2.Site{ID: "1", Ext: []byte(`{"a":1}`)}, + fpdConfig: &openrtb_ext.ORTB2{Site: json.RawMessage(`malformed`)}, + expectError: "invalid first party data ext", }, } for _, test := range testCases { t.Run(test.description, func(t *testing.T) { resultSite, err := resolveSite(test.fpdConfig, test.bidRequestSite, test.globalFPD, test.openRtbGlobalFPD, "bidderA") - if test.expectError { - assert.Error(t, err) + if len(test.expectError) > 0 { + assert.EqualError(t, err, test.expectError) } else { - assert.NoError(t, err, "unexpected error returned") + require.NoError(t, err, "unexpected error returned") assert.Equal(t, test.expectedSite, resultSite, "Result site is incorrect") } }) @@ -1023,7 +1060,7 @@ func TestResolveApp(t *testing.T) { globalFPD map[string][]byte openRtbGlobalFPD map[string][]openrtb2.Data expectedApp *openrtb2.App - expectError bool + expectError string }{ { description: "FPD config and bid request app are not specified", @@ -1031,42 +1068,42 @@ func TestResolveApp(t *testing.T) { }, { description: "FPD config app only is specified", - fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id": "test"}`)}, - expectError: true, + fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test"}`)}, + expectError: "incorrect First Party Data for bidder bidderA: App object is not defined in request, but defined in FPD config", }, { description: "FPD config and bid request app are specified", - fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id": "test1"}`)}, + fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test1"}`)}, bidRequestApp: &openrtb2.App{ID: "test2"}, expectedApp: &openrtb2.App{ID: "test1"}, }, { description: "FPD config, bid request and global fpd app are specified, no input app ext", - fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id": "test1"}`)}, + fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test1"}`)}, bidRequestApp: &openrtb2.App{ID: "test2"}, - globalFPD: map[string][]byte{appKey: []byte(`{"globalFPDAppData": "globalFPDAppDataValue"}`)}, + globalFPD: map[string][]byte{appKey: []byte(`{"globalFPDAppData":"globalFPDAppDataValue"}`)}, expectedApp: &openrtb2.App{ID: "test1", Ext: json.RawMessage(`{"data":{"globalFPDAppData":"globalFPDAppDataValue"}}`)}, }, { description: "FPD config, bid request app with ext and global fpd app are specified, no input app ext", - fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id": "test1"}`)}, + fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test1"}`)}, bidRequestApp: &openrtb2.App{ID: "test2", Ext: json.RawMessage(`{"test":{"inputFPDAppData":"inputFPDAppDataValue"}}`)}, - globalFPD: map[string][]byte{appKey: []byte(`{"globalFPDAppData": "globalFPDAppDataValue"}`)}, + globalFPD: map[string][]byte{appKey: []byte(`{"globalFPDAppData":"globalFPDAppDataValue"}`)}, expectedApp: &openrtb2.App{ID: "test1", Ext: json.RawMessage(`{"data":{"globalFPDAppData":"globalFPDAppDataValue"},"test":{"inputFPDAppData":"inputFPDAppDataValue"}}`)}, }, { description: "FPD config, bid request and global fpd app are specified, with input app ext.data", - fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id": "test1"}`)}, + fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test1"}`)}, bidRequestApp: &openrtb2.App{ID: "test2", Ext: json.RawMessage(`{"data":{"inputFPDAppData":"inputFPDAppDataValue"}}`)}, - globalFPD: map[string][]byte{appKey: []byte(`{"globalFPDAppData": "globalFPDAppDataValue"}`)}, + globalFPD: map[string][]byte{appKey: []byte(`{"globalFPDAppData":"globalFPDAppDataValue"}`)}, expectedApp: &openrtb2.App{ID: "test1", Ext: json.RawMessage(`{"data":{"globalFPDAppData":"globalFPDAppDataValue","inputFPDAppData":"inputFPDAppDataValue"}}`)}, }, { description: "FPD config, bid request and global fpd app are specified, with input app ext.data malformed", - fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id": "test1"}`)}, + fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test1"}`)}, bidRequestApp: &openrtb2.App{ID: "test2", Ext: json.RawMessage(`{"data":{"inputFPDAppData":"inputFPDAppDataValue"}}`)}, globalFPD: map[string][]byte{appKey: []byte(`malformed`)}, - expectError: true, + expectError: "invalid first party data ext", }, { description: "bid request and openrtb global fpd app are specified, no input app ext", @@ -1105,7 +1142,7 @@ func TestResolveApp(t *testing.T) { }, { description: "fpd config app, bid request and openrtb global fpd app are specified, no input app ext", - fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id": "test1"}`)}, + fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test1"}`)}, bidRequestApp: &openrtb2.App{ID: "test2"}, openRtbGlobalFPD: map[string][]openrtb2.Data{appContentDataKey: { {ID: "DataId1", Name: "Name1"}, @@ -1118,7 +1155,7 @@ func TestResolveApp(t *testing.T) { }, { description: "fpd config app with ext, bid request and openrtb global fpd app are specified, no input app ext", - fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id": "test1", "ext":{"test":1}}`)}, + fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test1","ext":{"test":1}}`)}, bidRequestApp: &openrtb2.App{ID: "test2"}, openRtbGlobalFPD: map[string][]openrtb2.Data{appContentDataKey: { {ID: "DataId1", Name: "Name1"}, @@ -1132,8 +1169,8 @@ func TestResolveApp(t *testing.T) { }, { description: "fpd config app with ext, bid request app with ext and openrtb global fpd app are specified, no input app ext", - fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id": "test1", "ext":{"test":1}}`)}, - bidRequestApp: &openrtb2.App{ID: "test2", Ext: json.RawMessage(`{"test":2, "key": "value"}`)}, + fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test1","ext":{"test":1}}`)}, + bidRequestApp: &openrtb2.App{ID: "test2", Ext: json.RawMessage(`{"test":2,"key":"value"}`)}, openRtbGlobalFPD: map[string][]openrtb2.Data{appContentDataKey: { {ID: "DataId1", Name: "Name1"}, {ID: "DataId2", Name: "Name2"}, @@ -1146,8 +1183,8 @@ func TestResolveApp(t *testing.T) { }, { description: "fpd config app with malformed ext, bid request app with ext and openrtb global fpd app are specified, no input app ext", - fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id": "test1", "ext":{malformed}}`)}, - bidRequestApp: &openrtb2.App{ID: "test2", Ext: json.RawMessage(`{"test":2, "key": "value"}`)}, + fpdConfig: &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test1","ext":{malformed}}`)}, + bidRequestApp: &openrtb2.App{ID: "test2", Ext: json.RawMessage(`{"test":2,"key":"value"}`)}, openRtbGlobalFPD: map[string][]openrtb2.Data{appContentDataKey: { {ID: "DataId1", Name: "Name1"}, {ID: "DataId2", Name: "Name2"}, @@ -1158,15 +1195,15 @@ func TestResolveApp(t *testing.T) { }}, Ext: json.RawMessage(`{"key":"value","test":1}`), }, - expectError: true, + expectError: "invalid first party data ext", }, } for _, test := range testCases { t.Run(test.description, func(t *testing.T) { resultApp, err := resolveApp(test.fpdConfig, test.bidRequestApp, test.globalFPD, test.openRtbGlobalFPD, "bidderA") - if test.expectError { - assert.Error(t, err) + if len(test.expectError) > 0 { + assert.EqualError(t, err, test.expectError) } else { assert.NoError(t, err) assert.Equal(t, test.expectedApp, resultApp, "Result app is incorrect") @@ -1183,28 +1220,28 @@ func TestBuildExtData(t *testing.T) { }{ { description: "Input object with int value", - input: []byte(`{"someData": 123}`), - expectedRes: `{"data": {"someData": 123}}`, + input: []byte(`{"someData":123}`), + expectedRes: `{"data":{"someData":123}}`, }, { description: "Input object with bool value", - input: []byte(`{"someData": true}`), - expectedRes: `{"data": {"someData": true}}`, + input: []byte(`{"someData":true}`), + expectedRes: `{"data":{"someData":true}}`, }, { description: "Input object with string value", - input: []byte(`{"someData": "true"}`), - expectedRes: `{"data": {"someData": "true"}}`, + input: []byte(`{"someData":"true"}`), + expectedRes: `{"data":{"someData":"true"}}`, }, { description: "No input object", input: []byte(`{}`), - expectedRes: `{"data": {}}`, + expectedRes: `{"data":{}}`, }, { description: "Input object with object value", - input: []byte(`{"someData": {"moreFpdData": "fpddata"}}`), - expectedRes: `{"data": {"someData": {"moreFpdData": "fpddata"}}}`, + input: []byte(`{"someData":{"moreFpdData":"fpddata"}}`), + expectedRes: `{"data":{"someData":{"moreFpdData":"fpddata"}}}`, }, } @@ -1214,695 +1251,6 @@ func TestBuildExtData(t *testing.T) { } } -func TestMergeUser(t *testing.T) { - testCases := []struct { - name string - givenUser openrtb2.User - givenFPD json.RawMessage - expectedUser openrtb2.User - expectError bool - }{ - { - name: "empty", - givenUser: openrtb2.User{}, - givenFPD: []byte(`{}`), - expectedUser: openrtb2.User{}, - }, - { - name: "toplevel", - givenUser: openrtb2.User{ID: "1"}, - givenFPD: []byte(`{"id":"2"}`), - expectedUser: openrtb2.User{ID: "2"}, - }, - { - name: "toplevel-ext", - givenUser: openrtb2.User{Ext: []byte(`{"a":1,"b":2}`)}, - givenFPD: []byte(`{"ext":{"b":100,"c":3}}`), - expectedUser: openrtb2.User{Ext: []byte(`{"a":1,"b":100,"c":3}`)}, - }, - { - name: "toplevel-ext-err", - givenUser: openrtb2.User{ID: "1", Ext: []byte(`malformed`)}, - givenFPD: []byte(`{"id":"2"}`), - expectError: true, - }, - { - name: "nested-geo", - givenUser: openrtb2.User{Geo: &openrtb2.Geo{Lat: 1}}, - givenFPD: []byte(`{"geo":{"lat": 2}}`), - expectedUser: openrtb2.User{Geo: &openrtb2.Geo{Lat: 2}}, - }, - { - name: "nested-geo-ext", - givenUser: openrtb2.User{Geo: &openrtb2.Geo{Ext: []byte(`{"a":1,"b":2}`)}}, - givenFPD: []byte(`{"geo":{"ext":{"b":100,"c":3}}}`), - expectedUser: openrtb2.User{Geo: &openrtb2.Geo{Ext: []byte(`{"a":1,"b":100,"c":3}`)}}, - }, - { - name: "toplevel-ext-and-nested-geo-ext", - givenUser: openrtb2.User{Ext: []byte(`{"a":1,"b":2}`), Geo: &openrtb2.Geo{Ext: []byte(`{"a":10,"b":20}`)}}, - givenFPD: []byte(`{"ext":{"b":100,"c":3}, "geo":{"ext":{"b":100,"c":3}}}`), - expectedUser: openrtb2.User{Ext: []byte(`{"a":1,"b":100,"c":3}`), Geo: &openrtb2.Geo{Ext: []byte(`{"a":10,"b":100,"c":3}`)}}, - }, - { - name: "nested-geo-ext-err", - givenUser: openrtb2.User{Geo: &openrtb2.Geo{Ext: []byte(`malformed`)}}, - givenFPD: []byte(`{"geo":{"ext":{"b":100,"c":3}}}`), - expectError: true, - }, - { - name: "fpd-err", - givenUser: openrtb2.User{ID: "1", Ext: []byte(`{"a":1}`)}, - givenFPD: []byte(`malformed`), - expectError: true, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - err := mergeUser(&test.givenUser, test.givenFPD) - - if test.expectError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.Equal(t, test.expectedUser, test.givenUser, "result user is incorrect") - } - }) - } -} - -func TestMergeApp(t *testing.T) { - testCases := []struct { - name string - givenApp openrtb2.App - givenFPD json.RawMessage - expectedApp openrtb2.App - expectError bool - }{ - { - name: "empty", - givenApp: openrtb2.App{}, - givenFPD: []byte(`{}`), - expectedApp: openrtb2.App{}, - }, - { - name: "toplevel", - givenApp: openrtb2.App{ID: "1"}, - givenFPD: []byte(`{"id":"2"}`), - expectedApp: openrtb2.App{ID: "2"}, - }, - { - name: "toplevel-ext", - givenApp: openrtb2.App{Ext: []byte(`{"a":1,"b":2}`)}, - givenFPD: []byte(`{"ext":{"b":100,"c":3}}`), - expectedApp: openrtb2.App{Ext: []byte(`{"a":1,"b":100,"c":3}`)}, - }, - { - name: "toplevel-ext-err", - givenApp: openrtb2.App{ID: "1", Ext: []byte(`malformed`)}, - givenFPD: []byte(`{"id":"2"}`), - expectError: true, - }, - { - name: "nested-publisher", - givenApp: openrtb2.App{Publisher: &openrtb2.Publisher{Name: "pub1"}}, - givenFPD: []byte(`{"publisher":{"name": "pub2"}}`), - expectedApp: openrtb2.App{Publisher: &openrtb2.Publisher{Name: "pub2"}}, - }, - { - name: "nested-content", - givenApp: openrtb2.App{Content: &openrtb2.Content{Title: "content1"}}, - givenFPD: []byte(`{"content":{"title": "content2"}}`), - expectedApp: openrtb2.App{Content: &openrtb2.Content{Title: "content2"}}, - }, - { - name: "nested-content-producer", - givenApp: openrtb2.App{Content: &openrtb2.Content{Title: "content1", Producer: &openrtb2.Producer{Name: "producer1"}}}, - givenFPD: []byte(`{"content":{"title": "content2", "producer":{"name":"producer2"}}}`), - expectedApp: openrtb2.App{Content: &openrtb2.Content{Title: "content2", Producer: &openrtb2.Producer{Name: "producer2"}}}, - }, - { - name: "nested-content-network", - givenApp: openrtb2.App{Content: &openrtb2.Content{Title: "content1", Network: &openrtb2.Network{Name: "network1"}}}, - givenFPD: []byte(`{"content":{"title": "content2", "network":{"name":"network2"}}}`), - expectedApp: openrtb2.App{Content: &openrtb2.Content{Title: "content2", Network: &openrtb2.Network{Name: "network2"}}}, - }, - { - name: "nested-content-channel", - givenApp: openrtb2.App{Content: &openrtb2.Content{Title: "content1", Channel: &openrtb2.Channel{Name: "channel1"}}}, - givenFPD: []byte(`{"content":{"title": "content2", "channel":{"name":"channel2"}}}`), - expectedApp: openrtb2.App{Content: &openrtb2.Content{Title: "content2", Channel: &openrtb2.Channel{Name: "channel2"}}}, - }, - { - name: "nested-publisher-ext", - givenApp: openrtb2.App{Publisher: &openrtb2.Publisher{Ext: []byte(`{"a":1,"b":2}`)}}, - givenFPD: []byte(`{"publisher":{"ext":{"b":100,"c":3}}}`), - expectedApp: openrtb2.App{Publisher: &openrtb2.Publisher{Ext: []byte(`{"a":1,"b":100,"c":3}`)}}, - }, - { - name: "nested-content-ext", - givenApp: openrtb2.App{Content: &openrtb2.Content{Ext: []byte(`{"a":1,"b":2}`)}}, - givenFPD: []byte(`{"content":{"ext":{"b":100,"c":3}}}`), - expectedApp: openrtb2.App{Content: &openrtb2.Content{Ext: []byte(`{"a":1,"b":100,"c":3}`)}}, - }, - { - name: "nested-content-producer-ext", - givenApp: openrtb2.App{Content: &openrtb2.Content{Producer: &openrtb2.Producer{Ext: []byte(`{"a":1,"b":2}`)}}}, - givenFPD: []byte(`{"content":{"producer":{"ext":{"b":100,"c":3}}}}`), - expectedApp: openrtb2.App{Content: &openrtb2.Content{Producer: &openrtb2.Producer{Ext: []byte(`{"a":1,"b":100,"c":3}`)}}}, - }, - { - name: "nested-content-network-ext", - givenApp: openrtb2.App{Content: &openrtb2.Content{Network: &openrtb2.Network{Ext: []byte(`{"a":1,"b":2}`)}}}, - givenFPD: []byte(`{"content":{"network":{"ext":{"b":100,"c":3}}}}`), - expectedApp: openrtb2.App{Content: &openrtb2.Content{Network: &openrtb2.Network{Ext: []byte(`{"a":1,"b":100,"c":3}`)}}}, - }, - { - name: "nested-content-channel-ext", - givenApp: openrtb2.App{Content: &openrtb2.Content{Channel: &openrtb2.Channel{Ext: []byte(`{"a":1,"b":2}`)}}}, - givenFPD: []byte(`{"content":{"channel":{"ext":{"b":100,"c":3}}}}`), - expectedApp: openrtb2.App{Content: &openrtb2.Content{Channel: &openrtb2.Channel{Ext: []byte(`{"a":1,"b":100,"c":3}`)}}}, - }, - { - name: "toplevel-ext-and-nested-publisher-ext", - givenApp: openrtb2.App{Ext: []byte(`{"a":1,"b":2}`), Publisher: &openrtb2.Publisher{Ext: []byte(`{"a":10,"b":20}`)}}, - givenFPD: []byte(`{"ext":{"b":100,"c":3}, "publisher":{"ext":{"b":100,"c":3}}}`), - expectedApp: openrtb2.App{Ext: []byte(`{"a":1,"b":100,"c":3}`), Publisher: &openrtb2.Publisher{Ext: []byte(`{"a":10,"b":100,"c":3}`)}}, - }, - { - name: "toplevel-ext-and-nested-content-ext", - givenApp: openrtb2.App{Ext: []byte(`{"a":1,"b":2}`), Content: &openrtb2.Content{Ext: []byte(`{"a":10,"b":20}`)}}, - givenFPD: []byte(`{"ext":{"b":100,"c":3}, "content":{"ext":{"b":100,"c":3}}}`), - expectedApp: openrtb2.App{Ext: []byte(`{"a":1,"b":100,"c":3}`), Content: &openrtb2.Content{Ext: []byte(`{"a":10,"b":100,"c":3}`)}}, - }, - { - name: "toplevel-ext-and-nested-content-producer-ext", - givenApp: openrtb2.App{Ext: []byte(`{"a":1,"b":2}`), Content: &openrtb2.Content{Producer: &openrtb2.Producer{Ext: []byte(`{"a":10,"b":20}`)}}}, - givenFPD: []byte(`{"ext":{"b":100,"c":3}, "content":{"producer": {"ext":{"b":100,"c":3}}}}`), - expectedApp: openrtb2.App{Ext: []byte(`{"a":1,"b":100,"c":3}`), Content: &openrtb2.Content{Producer: &openrtb2.Producer{Ext: []byte(`{"a":10,"b":100,"c":3}`)}}}, - }, - { - name: "toplevel-ext-and-nested-content-network-ext", - givenApp: openrtb2.App{Ext: []byte(`{"a":1,"b":2}`), Content: &openrtb2.Content{Network: &openrtb2.Network{Ext: []byte(`{"a":10,"b":20}`)}}}, - givenFPD: []byte(`{"ext":{"b":100,"c":3}, "content":{"network": {"ext":{"b":100,"c":3}}}}`), - expectedApp: openrtb2.App{Ext: []byte(`{"a":1,"b":100,"c":3}`), Content: &openrtb2.Content{Network: &openrtb2.Network{Ext: []byte(`{"a":10,"b":100,"c":3}`)}}}, - }, - { - name: "toplevel-ext-and-nested-content-channel-ext", - givenApp: openrtb2.App{Ext: []byte(`{"a":1,"b":2}`), Content: &openrtb2.Content{Channel: &openrtb2.Channel{Ext: []byte(`{"a":10,"b":20}`)}}}, - givenFPD: []byte(`{"ext":{"b":100,"c":3}, "content":{"channel": {"ext":{"b":100,"c":3}}}}`), - expectedApp: openrtb2.App{Ext: []byte(`{"a":1,"b":100,"c":3}`), Content: &openrtb2.Content{Channel: &openrtb2.Channel{Ext: []byte(`{"a":10,"b":100,"c":3}`)}}}, - }, - { - name: "nested-publisher-ext-err", - givenApp: openrtb2.App{Publisher: &openrtb2.Publisher{Ext: []byte(`malformed`)}}, - givenFPD: []byte(`{"publisher":{"ext":{"b":100,"c":3}}}`), - expectError: true, - }, - { - name: "nested-content-ext-err", - givenApp: openrtb2.App{Content: &openrtb2.Content{Ext: []byte(`malformed`)}}, - givenFPD: []byte(`{"content":{"ext":{"b":100,"c":3}}}`), - expectError: true, - }, - { - name: "nested-content-producer-ext-err", - givenApp: openrtb2.App{Content: &openrtb2.Content{Producer: &openrtb2.Producer{Ext: []byte(`malformed`)}}}, - givenFPD: []byte(`{"content":{"producer": {"ext":{"b":100,"c":3}}}}`), - expectError: true, - }, - { - name: "nested-content-network-ext-err", - givenApp: openrtb2.App{Content: &openrtb2.Content{Network: &openrtb2.Network{Ext: []byte(`malformed`)}}}, - givenFPD: []byte(`{"content":{"network": {"ext":{"b":100,"c":3}}}}`), - expectError: true, - }, - { - name: "nested-content-channel-ext-err", - givenApp: openrtb2.App{Content: &openrtb2.Content{Channel: &openrtb2.Channel{Ext: []byte(`malformed`)}}}, - givenFPD: []byte(`{"content":{"channelx": {"ext":{"b":100,"c":3}}}}`), - expectError: true, - }, - { - name: "fpd-err", - givenApp: openrtb2.App{ID: "1", Ext: []byte(`{"a":1}`)}, - givenFPD: []byte(`malformed`), - expectError: true, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - err := mergeApp(&test.givenApp, test.givenFPD) - - if test.expectError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.Equal(t, test.expectedApp, test.givenApp, " result app is incorrect") - } - }) - } -} - -func TestMergeSite(t *testing.T) { - testCases := []struct { - name string - givenSite openrtb2.Site - givenFPD json.RawMessage - expectedSite openrtb2.Site - expectError bool - }{ - { - name: "empty", - givenSite: openrtb2.Site{}, - givenFPD: []byte(`{}`), - expectError: true, - }, - { - name: "toplevel", - givenSite: openrtb2.Site{ID: "1"}, - givenFPD: []byte(`{"id":"2"}`), - expectedSite: openrtb2.Site{ID: "2"}, - }, - { - name: "toplevel-ext", - givenSite: openrtb2.Site{Page: "test.com/page", Ext: []byte(`{"a":1,"b":2}`)}, - givenFPD: []byte(`{"ext":{"b":100,"c":3}}`), - expectedSite: openrtb2.Site{Page: "test.com/page", Ext: []byte(`{"a":1,"b":100,"c":3}`)}, - }, - { - name: "toplevel-ext-err", - givenSite: openrtb2.Site{ID: "1", Ext: []byte(`malformed`)}, - givenFPD: []byte(`{"id":"2"}`), - expectError: true, - }, - { - name: "nested-publisher", - givenSite: openrtb2.Site{Page: "test.com/page", Publisher: &openrtb2.Publisher{Name: "pub1"}}, - givenFPD: []byte(`{"publisher":{"name": "pub2"}}`), - expectedSite: openrtb2.Site{Page: "test.com/page", Publisher: &openrtb2.Publisher{Name: "pub2"}}, - }, - { - name: "nested-content", - givenSite: openrtb2.Site{Page: "test.com/page", Content: &openrtb2.Content{Title: "content1"}}, - givenFPD: []byte(`{"content":{"title": "content2"}}`), - expectedSite: openrtb2.Site{Page: "test.com/page", Content: &openrtb2.Content{Title: "content2"}}, - }, - { - name: "nested-content-producer", - givenSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Title: "content1", Producer: &openrtb2.Producer{Name: "producer1"}}}, - givenFPD: []byte(`{"content":{"title": "content2", "producer":{"name":"producer2"}}}`), - expectedSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Title: "content2", Producer: &openrtb2.Producer{Name: "producer2"}}}, - }, - { - name: "nested-content-network", - givenSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Title: "content1", Network: &openrtb2.Network{Name: "network1"}}}, - givenFPD: []byte(`{"content":{"title": "content2", "network":{"name":"network2"}}}`), - expectedSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Title: "content2", Network: &openrtb2.Network{Name: "network2"}}}, - }, - { - name: "nested-content-channel", - givenSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Title: "content1", Channel: &openrtb2.Channel{Name: "channel1"}}}, - givenFPD: []byte(`{"content":{"title": "content2", "channel":{"name":"channel2"}}}`), - expectedSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Title: "content2", Channel: &openrtb2.Channel{Name: "channel2"}}}, - }, - { - name: "nested-publisher-ext", - givenSite: openrtb2.Site{ID: "1", Publisher: &openrtb2.Publisher{Ext: []byte(`{"a":1,"b":2}`)}}, - givenFPD: []byte(`{"publisher":{"ext":{"b":100,"c":3}}}`), - expectedSite: openrtb2.Site{ID: "1", Publisher: &openrtb2.Publisher{Ext: []byte(`{"a":1,"b":100,"c":3}`)}}, - }, - { - name: "nested-content-ext", - givenSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Ext: []byte(`{"a":1,"b":2}`)}}, - givenFPD: []byte(`{"content":{"ext":{"b":100,"c":3}}}`), - expectedSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Ext: []byte(`{"a":1,"b":100,"c":3}`)}}, - }, - { - name: "nested-content-producer-ext", - givenSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Producer: &openrtb2.Producer{Ext: []byte(`{"a":1,"b":2}`)}}}, - givenFPD: []byte(`{"content":{"producer":{"ext":{"b":100,"c":3}}}}`), - expectedSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Producer: &openrtb2.Producer{Ext: []byte(`{"a":1,"b":100,"c":3}`)}}}, - }, - { - name: "nested-content-network-ext", - givenSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Network: &openrtb2.Network{Ext: []byte(`{"a":1,"b":2}`)}}}, - givenFPD: []byte(`{"content":{"network":{"ext":{"b":100,"c":3}}}}`), - expectedSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Network: &openrtb2.Network{Ext: []byte(`{"a":1,"b":100,"c":3}`)}}}, - }, - { - name: "nested-content-channel-ext", - givenSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Channel: &openrtb2.Channel{Ext: []byte(`{"a":1,"b":2}`)}}}, - givenFPD: []byte(`{"content":{"channel":{"ext":{"b":100,"c":3}}}}`), - expectedSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Channel: &openrtb2.Channel{Ext: []byte(`{"a":1,"b":100,"c":3}`)}}}, - }, - { - name: "toplevel-ext-and-nested-publisher-ext", - givenSite: openrtb2.Site{ID: "1", Ext: []byte(`{"a":1,"b":2}`), Publisher: &openrtb2.Publisher{Ext: []byte(`{"a":10,"b":20}`)}}, - givenFPD: []byte(`{"ext":{"b":100,"c":3}, "publisher":{"ext":{"b":100,"c":3}}}`), - expectedSite: openrtb2.Site{ID: "1", Ext: []byte(`{"a":1,"b":100,"c":3}`), Publisher: &openrtb2.Publisher{Ext: []byte(`{"a":10,"b":100,"c":3}`)}}, - }, - { - name: "toplevel-ext-and-nested-content-ext", - givenSite: openrtb2.Site{ID: "1", Ext: []byte(`{"a":1,"b":2}`), Content: &openrtb2.Content{Ext: []byte(`{"a":10,"b":20}`)}}, - givenFPD: []byte(`{"ext":{"b":100,"c":3}, "content":{"ext":{"b":100,"c":3}}}`), - expectedSite: openrtb2.Site{ID: "1", Ext: []byte(`{"a":1,"b":100,"c":3}`), Content: &openrtb2.Content{Ext: []byte(`{"a":10,"b":100,"c":3}`)}}, - }, - { - name: "toplevel-ext-and-nested-content-producer-ext", - givenSite: openrtb2.Site{ID: "1", Ext: []byte(`{"a":1,"b":2}`), Content: &openrtb2.Content{Producer: &openrtb2.Producer{Ext: []byte(`{"a":10,"b":20}`)}}}, - givenFPD: []byte(`{"ext":{"b":100,"c":3}, "content":{"producer": {"ext":{"b":100,"c":3}}}}`), - expectedSite: openrtb2.Site{ID: "1", Ext: []byte(`{"a":1,"b":100,"c":3}`), Content: &openrtb2.Content{Producer: &openrtb2.Producer{Ext: []byte(`{"a":10,"b":100,"c":3}`)}}}, - }, - { - name: "toplevel-ext-and-nested-content-network-ext", - givenSite: openrtb2.Site{ID: "1", Ext: []byte(`{"a":1,"b":2}`), Content: &openrtb2.Content{Network: &openrtb2.Network{Ext: []byte(`{"a":10,"b":20}`)}}}, - givenFPD: []byte(`{"ext":{"b":100,"c":3}, "content":{"network": {"ext":{"b":100,"c":3}}}}`), - expectedSite: openrtb2.Site{ID: "1", Ext: []byte(`{"a":1,"b":100,"c":3}`), Content: &openrtb2.Content{Network: &openrtb2.Network{Ext: []byte(`{"a":10,"b":100,"c":3}`)}}}, - }, - { - name: "toplevel-ext-and-nested-content-channel-ext", - givenSite: openrtb2.Site{ID: "1", Ext: []byte(`{"a":1,"b":2}`), Content: &openrtb2.Content{Channel: &openrtb2.Channel{Ext: []byte(`{"a":10,"b":20}`)}}}, - givenFPD: []byte(`{"ext":{"b":100,"c":3}, "content":{"channel": {"ext":{"b":100,"c":3}}}}`), - expectedSite: openrtb2.Site{ID: "1", Ext: []byte(`{"a":1,"b":100,"c":3}`), Content: &openrtb2.Content{Channel: &openrtb2.Channel{Ext: []byte(`{"a":10,"b":100,"c":3}`)}}}, - }, - { - name: "nested-publisher-ext-err", - givenSite: openrtb2.Site{ID: "1", Publisher: &openrtb2.Publisher{Ext: []byte(`malformed`)}}, - givenFPD: []byte(`{"publisher":{"ext":{"b":100,"c":3}}}`), - expectError: true, - }, - { - name: "nested-content-ext-err", - givenSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Ext: []byte(`malformed`)}}, - givenFPD: []byte(`{"content":{"ext":{"b":100,"c":3}}}`), - expectError: true, - }, - { - name: "nested-content-producer-ext-err", - givenSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Producer: &openrtb2.Producer{Ext: []byte(`malformed`)}}}, - givenFPD: []byte(`{"content":{"producer": {"ext":{"b":100,"c":3}}}}`), - expectError: true, - }, - { - name: "nested-content-network-ext-err", - givenSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Network: &openrtb2.Network{Ext: []byte(`malformed`)}}}, - givenFPD: []byte(`{"content":{"network": {"ext":{"b":100,"c":3}}}}`), - expectError: true, - }, - { - name: "nested-content-channel-ext-err", - givenSite: openrtb2.Site{ID: "1", Content: &openrtb2.Content{Channel: &openrtb2.Channel{Ext: []byte(`malformed`)}}}, - givenFPD: []byte(`{"content":{"channelx": {"ext":{"b":100,"c":3}}}}`), - expectError: true, - }, - { - name: "fpd-err", - givenSite: openrtb2.Site{ID: "1", Ext: []byte(`{"a":1}`)}, - givenFPD: []byte(`malformed`), - expectError: true, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - err := mergeSite(&test.givenSite, test.givenFPD, "BidderA") - - if test.expectError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.Equal(t, test.expectedSite, test.givenSite, " result Site is incorrect") - } - }) - } -} - -// TestMergeObjectStructure detects when new nested objects are added to First Party Data supported -// fields, as these will invalidate the mergeSite, mergeApp, and mergeUser methods. If this test fails, -// fix the merge methods to add support and update this test to set a new baseline. -func TestMergeObjectStructure(t *testing.T) { - testCases := []struct { - name string - kind any - knownStructs []string - }{ - { - name: "Site", - kind: openrtb2.Site{}, - knownStructs: []string{ - "Publisher", - "Content", - "Content.Producer", - "Content.Network", - "Content.Channel", - }, - }, - { - name: "App", - kind: openrtb2.App{}, - knownStructs: []string{ - "Publisher", - "Content", - "Content.Producer", - "Content.Network", - "Content.Channel", - }, - }, - { - name: "User", - kind: openrtb2.User{}, - knownStructs: []string{ - "Geo", - }, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - nestedStructs := []string{} - - var discover func(parent string, t reflect.Type) - discover = func(parent string, t reflect.Type) { - fields := reflect.VisibleFields(t) - for _, field := range fields { - if field.Type.Kind() == reflect.Pointer && field.Type.Elem().Kind() == reflect.Struct { - nestedStructs = append(nestedStructs, parent+field.Name) - discover(parent+field.Name+".", field.Type.Elem()) - } - } - } - discover("", reflect.TypeOf(test.kind)) - - assert.ElementsMatch(t, test.knownStructs, nestedStructs) - }) - } -} - -// user memory protect test -func TestMergeUserMemoryProtection(t *testing.T) { - inputGeo := &openrtb2.Geo{ - Ext: json.RawMessage(`{"a":1,"b":2}`), - } - input := openrtb2.User{ - ID: "1", - Geo: inputGeo, - } - - err := mergeUser(&input, userFPD) - assert.NoError(t, err) - - // Input user object is expected to be a copy. Changes are ok. - assert.Equal(t, "2", input.ID, "user-id-copied") - - // Nested objects must be copied before changes. - assert.JSONEq(t, `{"a":1,"b":2}`, string(inputGeo.Ext), "geo-input") - assert.JSONEq(t, `{"a":1,"b":100,"c":3}`, string(input.Geo.Ext), "geo-copied") -} - -// app memory protect test -func TestMergeAppMemoryProtection(t *testing.T) { - inputPublisher := &openrtb2.Publisher{ - ID: "InPubId", - Ext: json.RawMessage(`{"a": "inputPubExt", "b": 1}`), - } - inputContent := &openrtb2.Content{ - ID: "InContentId", - Ext: json.RawMessage(`{"a": "inputContentExt", "b": 1}`), - Producer: &openrtb2.Producer{ - ID: "InProducerId", - Ext: json.RawMessage(`{"a": "inputProducerExt", "b": 1}`), - }, - Network: &openrtb2.Network{ - ID: "InNetworkId", - Ext: json.RawMessage(`{"a": "inputNetworkExt", "b": 1}`), - }, - Channel: &openrtb2.Channel{ - ID: "InChannelId", - Ext: json.RawMessage(`{"a": "inputChannelExt", "b": 1}`), - }, - } - input := openrtb2.App{ - ID: "InAppID", - Publisher: inputPublisher, - Content: inputContent, - Ext: json.RawMessage(`{"a": "inputAppExt", "b": 1}`), - } - - err := mergeApp(&input, fpdWithPublisherAndContent) - assert.NoError(t, err) - - // Input app object is expected to be a copy. Changes are ok. - assert.Equal(t, "FPDID", input.ID, "app-id-copied") - assert.JSONEq(t, `{"a": "FPDExt", "b": 2}`, string(input.Ext), "app-ext-copied") - - // Nested objects must be copied before changes. - assert.Equal(t, "InPubId", inputPublisher.ID, "app-pub-id-input") - assert.Equal(t, "FPDPubId", input.Publisher.ID, "app-pub-id-copied") - assert.JSONEq(t, `{"a": "inputPubExt", "b": 1}`, string(inputPublisher.Ext), "app-pub-ext-input") - assert.JSONEq(t, `{"a": "FPDPubExt", "b": 2}`, string(input.Publisher.Ext), "app-pub-ext-copied") - - assert.Equal(t, "InContentId", inputContent.ID, "app-content-id-input") - assert.Equal(t, "FPDContentId", input.Content.ID, "app-content-id-copied") - assert.JSONEq(t, `{"a": "inputContentExt", "b": 1}`, string(inputContent.Ext), "app-content-ext-input") - assert.JSONEq(t, `{"a": "FPDContentExt", "b": 2}`, string(input.Content.Ext), "app-content-ext-copied") - - assert.Equal(t, "InProducerId", inputContent.Producer.ID, "app-content-producer-id-input") - assert.Equal(t, "FPDProducerId", input.Content.Producer.ID, "app-content-producer-id-copied") - assert.JSONEq(t, `{"a": "inputProducerExt", "b": 1}`, string(inputContent.Producer.Ext), "app-content-producer-ext-input") - assert.JSONEq(t, `{"a": "FPDProducerExt", "b": 2}`, string(input.Content.Producer.Ext), "app-content-producer-ext-copied") - - assert.Equal(t, "InNetworkId", inputContent.Network.ID, "app-content-network-id-input") - assert.Equal(t, "FPDNetworkId", input.Content.Network.ID, "app-content-network-id-copied") - assert.JSONEq(t, `{"a": "inputNetworkExt", "b": 1}`, string(inputContent.Network.Ext), "app-content-network-ext-input") - assert.JSONEq(t, `{"a": "FPDNetworkExt", "b": 2}`, string(input.Content.Network.Ext), "app-content-network-ext-copied") - - assert.Equal(t, "InChannelId", inputContent.Channel.ID, "app-content-channel-id-input") - assert.Equal(t, "FPDChannelId", input.Content.Channel.ID, "app-content-channel-id-copied") - assert.JSONEq(t, `{"a": "inputChannelExt", "b": 1}`, string(inputContent.Channel.Ext), "app-content-channel-ext-input") - assert.JSONEq(t, `{"a": "FPDChannelExt", "b": 2}`, string(input.Content.Channel.Ext), "app-content-channel-ext-copied") -} - -// site memory protect test -func TestMergeSiteMemoryProtection(t *testing.T) { - inputPublisher := &openrtb2.Publisher{ - ID: "InPubId", - Ext: json.RawMessage(`{"a": "inputPubExt", "b": 1}`), - } - inputContent := &openrtb2.Content{ - ID: "InContentId", - Ext: json.RawMessage(`{"a": "inputContentExt", "b": 1}`), - Producer: &openrtb2.Producer{ - ID: "InProducerId", - Ext: json.RawMessage(`{"a": "inputProducerExt", "b": 1}`), - }, - Network: &openrtb2.Network{ - ID: "InNetworkId", - Ext: json.RawMessage(`{"a": "inputNetworkExt", "b": 1}`), - }, - Channel: &openrtb2.Channel{ - ID: "InChannelId", - Ext: json.RawMessage(`{"a": "inputChannelExt", "b": 1}`), - }, - } - input := openrtb2.Site{ - ID: "InSiteID", - Publisher: inputPublisher, - Content: inputContent, - Ext: json.RawMessage(`{"a": "inputSiteExt", "b": 1}`), - } - - err := mergeSite(&input, fpdWithPublisherAndContent, "BidderA") - assert.NoError(t, err) - - // Input app object is expected to be a copy. Changes are ok. - assert.Equal(t, "FPDID", input.ID, "site-id-copied") - assert.JSONEq(t, `{"a": "FPDExt", "b": 2}`, string(input.Ext), "site-ext-copied") - - // Nested objects must be copied before changes. - assert.Equal(t, "InPubId", inputPublisher.ID, "site-pub-id-input") - assert.Equal(t, "FPDPubId", input.Publisher.ID, "site-pub-id-copied") - assert.JSONEq(t, `{"a": "inputPubExt", "b": 1}`, string(inputPublisher.Ext), "site-pub-ext-input") - assert.JSONEq(t, `{"a": "FPDPubExt", "b": 2}`, string(input.Publisher.Ext), "site-pub-ext-copied") - - assert.Equal(t, "InContentId", inputContent.ID, "site-content-id-input") - assert.Equal(t, "FPDContentId", input.Content.ID, "site-content-id-copied") - assert.JSONEq(t, `{"a": "inputContentExt", "b": 1}`, string(inputContent.Ext), "site-content-ext-input") - assert.JSONEq(t, `{"a": "FPDContentExt", "b": 2}`, string(input.Content.Ext), "site-content-ext-copied") - - assert.Equal(t, "InProducerId", inputContent.Producer.ID, "site-content-producer-id-input") - assert.Equal(t, "FPDProducerId", input.Content.Producer.ID, "site-content-producer-id-copied") - assert.JSONEq(t, `{"a": "inputProducerExt", "b": 1}`, string(inputContent.Producer.Ext), "site-content-producer-ext-input") - assert.JSONEq(t, `{"a": "FPDProducerExt", "b": 2}`, string(input.Content.Producer.Ext), "site-content-producer-ext-copied") - - assert.Equal(t, "InNetworkId", inputContent.Network.ID, "site-content-network-id-input") - assert.Equal(t, "FPDNetworkId", input.Content.Network.ID, "site-content-network-id-copied") - assert.JSONEq(t, `{"a": "inputNetworkExt", "b": 1}`, string(inputContent.Network.Ext), "site-content-network-ext-input") - assert.JSONEq(t, `{"a": "FPDNetworkExt", "b": 2}`, string(input.Content.Network.Ext), "site-content-network-ext-copied") - - assert.Equal(t, "InChannelId", inputContent.Channel.ID, "site-content-channel-id-input") - assert.Equal(t, "FPDChannelId", input.Content.Channel.ID, "site-content-channel-id-copied") - assert.JSONEq(t, `{"a": "inputChannelExt", "b": 1}`, string(inputContent.Channel.Ext), "site-content-channel-ext-input") - assert.JSONEq(t, `{"a": "FPDChannelExt", "b": 2}`, string(input.Content.Channel.Ext), "site-content-channel-ext-copied") -} - -var ( - userFPD = []byte(` -{ - "id": "2", - "geo": { - "ext": { - "b": 100, - "c": 3 - } - } -} -`) - - fpdWithPublisherAndContent = []byte(` -{ - "id": "FPDID", - "ext": {"a": "FPDExt", "b": 2}, - "publisher": { - "id": "FPDPubId", - "ext": {"a": "FPDPubExt", "b": 2} - }, - "content": { - "id": "FPDContentId", - "ext": {"a": "FPDContentExt", "b": 2}, - "producer": { - "id": "FPDProducerId", - "ext": {"a": "FPDProducerExt", "b": 2} - }, - "network": { - "id": "FPDNetworkId", - "ext": {"a": "FPDNetworkExt", "b": 2} - }, - "channel": { - "id": "FPDChannelId", - "ext": {"a": "FPDChannelExt", "b": 2} - } - } -} -`) - - user = []byte(` -{ - "id": "2", - "yob": 2000, - "geo": { - "city": "LA", - "ext": { - "b": 100, - "c": 3 - } - } -} -`) -) - func loadTestFile[T any](filename string) (T, error) { var testFile T diff --git a/floors/enforce.go b/floors/enforce.go index 9318c9d278e..f72d2bb72c5 100644 --- a/floors/enforce.go +++ b/floors/enforce.go @@ -5,11 +5,11 @@ import ( "fmt" "math/rand" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // Enforce does floors enforcement for bids from all bidders based on floors provided in request, account level floors config diff --git a/floors/enforce_test.go b/floors/enforce_test.go index 725ac22b193..3da86e2d74a 100644 --- a/floors/enforce_test.go +++ b/floors/enforce_test.go @@ -6,12 +6,12 @@ import ( "reflect" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -765,7 +765,7 @@ func TestUpdateBidExtWithFloors(t *testing.T) { { name: "Valid prebid extension in imp.ext", args: args{ - reqImp: &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{ID: "1234", Video: &openrtb2.Video{W: 300, H: 250}, Ext: []byte(`{"prebid":{"floors":{"floorrule":"test|123|xyz","floorrulevalue":5.5,"floorvalue":5.5}}}`)}}, + reqImp: &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{ID: "1234", Video: &openrtb2.Video{W: ptrutil.ToPtr[int64](300), H: ptrutil.ToPtr[int64](250)}, Ext: []byte(`{"prebid":{"floors":{"floorrule":"test|123|xyz","floorrulevalue":5.5,"floorvalue":5.5}}}`)}}, bid: &entities.PbsOrtbBid{ Bid: &openrtb2.Bid{ Price: 10.10, diff --git a/floors/fetcher.go b/floors/fetcher.go new file mode 100644 index 00000000000..9fa6ad4f380 --- /dev/null +++ b/floors/fetcher.go @@ -0,0 +1,335 @@ +package floors + +import ( + "container/heap" + "context" + "encoding/json" + "errors" + "io" + "math" + "net/http" + "strconv" + "time" + + "github.com/alitto/pond" + validator "github.com/asaskevich/govalidator" + "github.com/coocood/freecache" + "github.com/golang/glog" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/timeutil" +) + +var refetchCheckInterval = 300 + +type fetchInfo struct { + config.AccountFloorFetch + fetchTime int64 + refetchRequest bool + retryCount int +} + +type WorkerPool interface { + TrySubmit(task func()) bool + Stop() +} + +type FloorFetcher interface { + Fetch(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) + Stop() +} + +type PriceFloorFetcher struct { + pool WorkerPool // Goroutines worker pool + fetchQueue FetchQueue // Priority Queue to fetch floor data + fetchInProgress map[string]bool // Map of URL with fetch status + configReceiver chan fetchInfo // Channel which recieves URLs to be fetched + done chan struct{} // Channel to close fetcher + cache *freecache.Cache // cache + httpClient *http.Client // http client to fetch data from url + time timeutil.Time // time interface to record request timings + metricEngine metrics.MetricsEngine // Records malfunctions in dynamic fetch + maxRetries int // Max number of retries for failing URLs +} + +type FetchQueue []*fetchInfo + +func (fq FetchQueue) Len() int { + return len(fq) +} + +func (fq FetchQueue) Less(i, j int) bool { + return fq[i].fetchTime < fq[j].fetchTime +} + +func (fq FetchQueue) Swap(i, j int) { + fq[i], fq[j] = fq[j], fq[i] +} + +func (fq *FetchQueue) Push(element interface{}) { + fetchInfo := element.(*fetchInfo) + *fq = append(*fq, fetchInfo) +} + +func (fq *FetchQueue) Pop() interface{} { + old := *fq + n := len(old) + fetchInfo := old[n-1] + old[n-1] = nil + *fq = old[0 : n-1] + return fetchInfo +} + +func (fq *FetchQueue) Top() *fetchInfo { + old := *fq + if len(old) == 0 { + return nil + } + return old[0] +} + +func workerPanicHandler(p interface{}) { + glog.Errorf("floor fetcher worker panicked: %v", p) +} + +func NewPriceFloorFetcher(config config.PriceFloors, httpClient *http.Client, metricEngine metrics.MetricsEngine) *PriceFloorFetcher { + if !config.Enabled { + return nil + } + + floorFetcher := PriceFloorFetcher{ + pool: pond.New(config.Fetcher.Worker, config.Fetcher.Capacity, pond.PanicHandler(workerPanicHandler)), + fetchQueue: make(FetchQueue, 0, 100), + fetchInProgress: make(map[string]bool), + configReceiver: make(chan fetchInfo, config.Fetcher.Capacity), + done: make(chan struct{}), + cache: freecache.NewCache(config.Fetcher.CacheSize * 1024 * 1024), + httpClient: httpClient, + time: &timeutil.RealTime{}, + metricEngine: metricEngine, + maxRetries: config.Fetcher.MaxRetries, + } + + go floorFetcher.Fetcher() + + return &floorFetcher +} + +func (f *PriceFloorFetcher) SetWithExpiry(key string, value json.RawMessage, cacheExpiry int) { + f.cache.Set([]byte(key), value, cacheExpiry) +} + +func (f *PriceFloorFetcher) Get(key string) (json.RawMessage, bool) { + data, err := f.cache.Get([]byte(key)) + if err != nil { + return nil, false + } + + return data, true +} + +func (f *PriceFloorFetcher) Fetch(config config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) { + if f == nil || !config.UseDynamicData || len(config.Fetcher.URL) == 0 || !validator.IsURL(config.Fetcher.URL) { + return nil, openrtb_ext.FetchNone + } + + // Check for floors JSON in cache + if result, found := f.Get(config.Fetcher.URL); found { + var fetchedFloorData openrtb_ext.PriceFloorRules + if err := json.Unmarshal(result, &fetchedFloorData); err != nil || fetchedFloorData.Data == nil { + return nil, openrtb_ext.FetchError + } + return &fetchedFloorData, openrtb_ext.FetchSuccess + } + + //miss: push to channel to fetch and return empty response + if config.Enabled && config.Fetcher.Enabled && config.Fetcher.Timeout > 0 { + fetchConfig := fetchInfo{AccountFloorFetch: config.Fetcher, fetchTime: f.time.Now().Unix(), refetchRequest: false, retryCount: 0} + f.configReceiver <- fetchConfig + } + + return nil, openrtb_ext.FetchInprogress +} + +func (f *PriceFloorFetcher) worker(fetchConfig fetchInfo) { + floorData, fetchedMaxAge := f.fetchAndValidate(fetchConfig.AccountFloorFetch) + if floorData != nil { + // Reset retry count when data is successfully fetched + fetchConfig.retryCount = 0 + + // Update cache with new floor rules + cacheExpiry := fetchConfig.AccountFloorFetch.MaxAge + if fetchedMaxAge != 0 { + cacheExpiry = fetchedMaxAge + } + floorData, err := json.Marshal(floorData) + if err != nil { + glog.Errorf("Error while marshaling fetched floor data for url %s", fetchConfig.AccountFloorFetch.URL) + } else { + f.SetWithExpiry(fetchConfig.AccountFloorFetch.URL, floorData, cacheExpiry) + } + } else { + fetchConfig.retryCount++ + } + + // Send to refetch channel + if fetchConfig.retryCount < f.maxRetries { + fetchConfig.fetchTime = f.time.Now().Add(time.Duration(fetchConfig.AccountFloorFetch.Period) * time.Second).Unix() + fetchConfig.refetchRequest = true + f.configReceiver <- fetchConfig + } +} + +// Stop terminates price floor fetcher +func (f *PriceFloorFetcher) Stop() { + if f == nil { + return + } + + close(f.done) + f.pool.Stop() + close(f.configReceiver) +} + +func (f *PriceFloorFetcher) submit(fetchConfig *fetchInfo) { + status := f.pool.TrySubmit(func() { + f.worker(*fetchConfig) + }) + if !status { + heap.Push(&f.fetchQueue, fetchConfig) + } +} + +func (f *PriceFloorFetcher) Fetcher() { + //Create Ticker of 5 minutes + ticker := time.NewTicker(time.Duration(refetchCheckInterval) * time.Second) + + for { + select { + case fetchConfig := <-f.configReceiver: + if fetchConfig.refetchRequest { + heap.Push(&f.fetchQueue, &fetchConfig) + } else { + if _, ok := f.fetchInProgress[fetchConfig.URL]; !ok { + f.fetchInProgress[fetchConfig.URL] = true + f.submit(&fetchConfig) + } + } + case <-ticker.C: + currentTime := f.time.Now().Unix() + for top := f.fetchQueue.Top(); top != nil && top.fetchTime <= currentTime; top = f.fetchQueue.Top() { + nextFetch := heap.Pop(&f.fetchQueue) + f.submit(nextFetch.(*fetchInfo)) + } + case <-f.done: + ticker.Stop() + glog.Info("Price Floor fetcher terminated") + return + } + } +} + +func (f *PriceFloorFetcher) fetchAndValidate(config config.AccountFloorFetch) (*openrtb_ext.PriceFloorRules, int) { + floorResp, maxAge, err := f.fetchFloorRulesFromURL(config) + if floorResp == nil || err != nil { + glog.Errorf("Error while fetching floor data from URL: %s, reason : %s", config.URL, err.Error()) + return nil, 0 + } + + if len(floorResp) > (config.MaxFileSizeKB * 1024) { + glog.Errorf("Recieved invalid floor data from URL: %s, reason : floor file size is greater than MaxFileSize", config.URL) + return nil, 0 + } + + var priceFloors openrtb_ext.PriceFloorRules + if err = json.Unmarshal(floorResp, &priceFloors.Data); err != nil { + glog.Errorf("Recieved invalid price floor json from URL: %s", config.URL) + return nil, 0 + } + + if err := validateRules(config, &priceFloors); err != nil { + glog.Errorf("Validation failed for floor JSON from URL: %s, reason: %s", config.URL, err.Error()) + return nil, 0 + } + + return &priceFloors, maxAge +} + +// fetchFloorRulesFromURL returns a price floor JSON and time for which this JSON is valid +// from provided URL with timeout constraints +func (f *PriceFloorFetcher) fetchFloorRulesFromURL(config config.AccountFloorFetch) ([]byte, int, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(config.Timeout)*time.Millisecond) + defer cancel() + + httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, config.URL, nil) + if err != nil { + return nil, 0, errors.New("error while forming http fetch request : " + err.Error()) + } + + httpResp, err := f.httpClient.Do(httpReq) + if err != nil { + return nil, 0, errors.New("error while getting response from url : " + err.Error()) + } + + if httpResp.StatusCode != http.StatusOK { + return nil, 0, errors.New("no response from server") + } + + var maxAge int + if maxAgeStr := httpResp.Header.Get("max-age"); maxAgeStr != "" { + maxAge, err = strconv.Atoi(maxAgeStr) + if err != nil { + glog.Errorf("max-age in header is malformed for url %s", config.URL) + } + if maxAge <= config.Period || maxAge > math.MaxInt32 { + glog.Errorf("Invalid max-age = %s provided, value should be valid integer and should be within (%v, %v)", maxAgeStr, config.Period, math.MaxInt32) + } + } + + respBody, err := io.ReadAll(httpResp.Body) + if err != nil { + return nil, 0, errors.New("unable to read response") + } + defer httpResp.Body.Close() + + return respBody, maxAge, nil +} + +func validateRules(config config.AccountFloorFetch, priceFloors *openrtb_ext.PriceFloorRules) error { + if priceFloors.Data == nil { + return errors.New("empty data in floor JSON") + } + + if len(priceFloors.Data.ModelGroups) == 0 { + return errors.New("no model groups found in price floor data") + } + + if priceFloors.Data.SkipRate < 0 || priceFloors.Data.SkipRate > 100 { + return errors.New("skip rate should be greater than or equal to 0 and less than 100") + } + + if priceFloors.Data.UseFetchDataRate != nil && (*priceFloors.Data.UseFetchDataRate < dataRateMin || *priceFloors.Data.UseFetchDataRate > dataRateMax) { + return errors.New("usefetchdatarate should be greater than or equal to 0 and less than or equal to 100") + } + + for _, modelGroup := range priceFloors.Data.ModelGroups { + if len(modelGroup.Values) == 0 || len(modelGroup.Values) > config.MaxRules { + return errors.New("invalid number of floor rules, floor rules should be greater than zero and less than MaxRules specified in account config") + } + + if modelGroup.ModelWeight != nil && (*modelGroup.ModelWeight < 1 || *modelGroup.ModelWeight > 100) { + return errors.New("modelGroup[].modelWeight should be greater than or equal to 1 and less than 100") + } + + if modelGroup.SkipRate < 0 || modelGroup.SkipRate > 100 { + return errors.New("model group skip rate should be greater than or equal to 0 and less than 100") + } + + if modelGroup.Default < 0 { + return errors.New("modelGroup.Default should be greater than 0") + } + } + + return nil +} diff --git a/floors/fetcher_test.go b/floors/fetcher_test.go new file mode 100644 index 00000000000..83876dc48b7 --- /dev/null +++ b/floors/fetcher_test.go @@ -0,0 +1,1307 @@ +package floors + +import ( + "encoding/json" + "fmt" + "math/rand" + "net/http" + "net/http/httptest" + "reflect" + "testing" + "time" + + "github.com/alitto/pond" + "github.com/coocood/freecache" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/metrics" + metricsConf "github.com/prebid/prebid-server/v3/metrics/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/ptrutil" + "github.com/prebid/prebid-server/v3/util/timeutil" + "github.com/stretchr/testify/assert" +) + +const MaxAge = "max-age" + +func TestFetchQueueLen(t *testing.T) { + tests := []struct { + name string + fq FetchQueue + want int + }{ + { + name: "Queue is empty", + fq: make(FetchQueue, 0), + want: 0, + }, + { + name: "Queue is of lenght 1", + fq: make(FetchQueue, 1), + want: 1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fq.Len(); got != tt.want { + t.Errorf("FetchQueue.Len() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFetchQueueLess(t *testing.T) { + type args struct { + i int + j int + } + tests := []struct { + name string + fq FetchQueue + args args + want bool + }{ + { + name: "first fetchperiod is less than second", + fq: FetchQueue{&fetchInfo{fetchTime: 10}, &fetchInfo{fetchTime: 20}}, + args: args{i: 0, j: 1}, + want: true, + }, + { + name: "first fetchperiod is greater than second", + fq: FetchQueue{&fetchInfo{fetchTime: 30}, &fetchInfo{fetchTime: 10}}, + args: args{i: 0, j: 1}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fq.Less(tt.args.i, tt.args.j); got != tt.want { + t.Errorf("FetchQueue.Less() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFetchQueueSwap(t *testing.T) { + type args struct { + i int + j int + } + tests := []struct { + name string + fq FetchQueue + args args + }{ + { + name: "Swap two elements at index i and j", + fq: FetchQueue{&fetchInfo{fetchTime: 30}, &fetchInfo{fetchTime: 10}}, + args: args{i: 0, j: 1}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fInfo1, fInfo2 := tt.fq[0], tt.fq[1] + tt.fq.Swap(tt.args.i, tt.args.j) + assert.Equal(t, fInfo1, tt.fq[1], "elements are not swapped") + assert.Equal(t, fInfo2, tt.fq[0], "elements are not swapped") + }) + } +} + +func TestFetchQueuePush(t *testing.T) { + type args struct { + element interface{} + } + tests := []struct { + name string + fq *FetchQueue + args args + }{ + { + name: "Push element to queue", + fq: &FetchQueue{}, + args: args{element: &fetchInfo{fetchTime: 10}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.fq.Push(tt.args.element) + q := *tt.fq + assert.Equal(t, q[0], &fetchInfo{fetchTime: 10}) + }) + } +} + +func TestFetchQueuePop(t *testing.T) { + tests := []struct { + name string + fq *FetchQueue + want interface{} + }{ + { + name: "Pop element from queue", + fq: &FetchQueue{&fetchInfo{fetchTime: 10}}, + want: &fetchInfo{fetchTime: 10}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fq.Pop(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("FetchQueue.Pop() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFetchQueueTop(t *testing.T) { + tests := []struct { + name string + fq *FetchQueue + want *fetchInfo + }{ + { + name: "Get top element from queue", + fq: &FetchQueue{&fetchInfo{fetchTime: 20}}, + want: &fetchInfo{fetchTime: 20}, + }, + { + name: "Queue is empty", + fq: &FetchQueue{}, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fq.Top(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("FetchQueue.Top() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestValidatePriceFloorRules(t *testing.T) { + var zero = 0 + var one_o_one = 101 + var testURL = "abc.com" + type args struct { + configs config.AccountFloorFetch + priceFloors *openrtb_ext.PriceFloorRules + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "Price floor data is empty", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 5, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{}, + }, + wantErr: true, + }, + { + name: "Model group array is empty", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 5, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{}, + }, + }, + wantErr: true, + }, + { + name: "floor rules is empty", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 5, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{}, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "floor rules is grater than max floor rules", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 0, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "Modelweight is zero", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 1, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + ModelWeight: &zero, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "Modelweight is 101", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 1, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + ModelWeight: &one_o_one, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "skiprate is 101", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 1, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + SkipRate: 101, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "Default is -1", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 1, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + Default: -1, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "Invalid skip rate in data", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 1, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + SkipRate: -44, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + }}, + }, + }, + }, + wantErr: true, + }, + { + name: "Invalid UseFetchDataRate", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + URL: testURL, + Timeout: 5, + MaxFileSizeKB: 20, + MaxRules: 1, + MaxAge: 20, + Period: 10, + }, + priceFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + SkipRate: 10, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + Values: map[string]float64{ + "*|*|www.website.com": 15.01, + }, + }}, + UseFetchDataRate: ptrutil.ToPtr(-11), + }, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := validateRules(tt.args.configs, tt.args.priceFloors); (err != nil) != tt.wantErr { + t.Errorf("validatePriceFloorRules() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestFetchFloorRulesFromURL(t *testing.T) { + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add("Content-Length", "645") + w.Header().Add(MaxAge, "20") + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + type args struct { + configs config.AccountFloorFetch + } + tests := []struct { + name string + args args + response []byte + responseStatus int + want []byte + want1 int + wantErr bool + }{ + { + name: "Floor data is successfully returned", + args: args{ + configs: config.AccountFloorFetch{ + URL: "", + Timeout: 60, + Period: 300, + }, + }, + response: func() []byte { + data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` + return []byte(data) + }(), + responseStatus: 200, + want: func() []byte { + data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` + return []byte(data) + }(), + want1: 20, + wantErr: false, + }, + { + name: "Time out occured", + args: args{ + configs: config.AccountFloorFetch{ + URL: "", + Timeout: 0, + Period: 300, + }, + }, + want1: 0, + responseStatus: 200, + wantErr: true, + }, + { + name: "Invalid URL", + args: args{ + configs: config.AccountFloorFetch{ + URL: "%%", + Timeout: 10, + Period: 300, + }, + }, + want1: 0, + responseStatus: 200, + wantErr: true, + }, + { + name: "No response from server", + args: args{ + configs: config.AccountFloorFetch{ + URL: "", + Timeout: 10, + Period: 300, + }, + }, + want1: 0, + responseStatus: 500, + wantErr: true, + }, + { + name: "Invalid response", + args: args{ + configs: config.AccountFloorFetch{ + URL: "", + Timeout: 10, + Period: 300, + }, + }, + want1: 0, + response: []byte("1"), + responseStatus: 200, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHttpServer := httptest.NewServer(mockHandler(tt.response, tt.responseStatus)) + defer mockHttpServer.Close() + + if tt.args.configs.URL == "" { + tt.args.configs.URL = mockHttpServer.URL + } + pff := PriceFloorFetcher{ + httpClient: mockHttpServer.Client(), + } + got, got1, err := pff.fetchFloorRulesFromURL(tt.args.configs) + if (err != nil) != tt.wantErr { + t.Errorf("fetchFloorRulesFromURL() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("fetchFloorRulesFromURL() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("fetchFloorRulesFromURL() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func TestFetchFloorRulesFromURLInvalidMaxAge(t *testing.T) { + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add("Content-Length", "645") + w.Header().Add(MaxAge, "abc") + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + type args struct { + configs config.AccountFloorFetch + } + tests := []struct { + name string + args args + response []byte + responseStatus int + want []byte + want1 int + wantErr bool + }{ + { + name: "Floor data is successfully returned", + args: args{ + configs: config.AccountFloorFetch{ + URL: "", + Timeout: 60, + Period: 300, + }, + }, + response: func() []byte { + data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` + return []byte(data) + }(), + responseStatus: 200, + want: func() []byte { + data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` + return []byte(data) + }(), + want1: 0, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHttpServer := httptest.NewServer(mockHandler(tt.response, tt.responseStatus)) + defer mockHttpServer.Close() + + if tt.args.configs.URL == "" { + tt.args.configs.URL = mockHttpServer.URL + } + + ppf := PriceFloorFetcher{ + httpClient: mockHttpServer.Client(), + } + got, got1, err := ppf.fetchFloorRulesFromURL(tt.args.configs) + if (err != nil) != tt.wantErr { + t.Errorf("fetchFloorRulesFromURL() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("fetchFloorRulesFromURL() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("fetchFloorRulesFromURL() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func TestFetchAndValidate(t *testing.T) { + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add(MaxAge, "30") + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + type args struct { + configs config.AccountFloorFetch + } + tests := []struct { + name string + args args + response []byte + responseStatus int + want *openrtb_ext.PriceFloorRules + want1 int + }{ + { + name: "Recieved valid price floor rules response", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + Timeout: 30, + MaxFileSizeKB: 700, + MaxRules: 30, + MaxAge: 60, + Period: 40, + }, + }, + response: func() []byte { + data := `{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}` + return []byte(data) + }(), + responseStatus: 200, + want: func() *openrtb_ext.PriceFloorRules { + var res openrtb_ext.PriceFloorRules + data := `{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}` + _ = json.Unmarshal([]byte(data), &res.Data) + return &res + }(), + want1: 30, + }, + { + name: "No response from server", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + Timeout: 30, + MaxFileSizeKB: 700, + MaxRules: 30, + MaxAge: 60, + Period: 40, + }, + }, + response: []byte{}, + responseStatus: 500, + want: nil, + want1: 0, + }, + { + name: "File is greater than MaxFileSize", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + Timeout: 30, + MaxFileSizeKB: 1, + MaxRules: 30, + MaxAge: 60, + Period: 40, + }, + }, + response: func() []byte { + data := `{"currency":"USD","floorProvider":"PM","floorsSchemaVersion":2,"modelGroups":[{"modelVersion":"M_0","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":0.85,"www.missyusa.com":0.7}},{"modelVersion":"M_1","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":1,"www.missyusa.com":1.85}},{"modelVersion":"M_2","modelWeight":5,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.6,"www.missyusa.com":0.7}},{"modelVersion":"M_3","modelWeight":2,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.9,"www.missyusa.com":0.75}},{"modelVersion":"M_4","modelWeight":1,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":1.35,"missyusa.com":1.75}},{"modelVersion":"M_5","modelWeight":2,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.4,"www.missyusa.com":0.9}},{"modelVersion":"M_6","modelWeight":43,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":2,"missyusa.com":2}},{"modelVersion":"M_7","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.4,"www.missyusa.com":1.85}},{"modelVersion":"M_8","modelWeight":3,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":1.7,"missyusa.com":0.1}},{"modelVersion":"M_9","modelWeight":7,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.9,"www.missyusa.com":1.05}},{"modelVersion":"M_10","modelWeight":9,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":2,"missyusa.com":0.1}},{"modelVersion":"M_11","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":0.45,"www.missyusa.com":1.5}},{"modelVersion":"M_12","modelWeight":8,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.2,"www.missyusa.com":1.7}},{"modelVersion":"M_13","modelWeight":8,"schema":{"fields":["domain"]},"values":{"missyusa.com":0.85,"www.missyusa.com":0.75}},{"modelVersion":"M_14","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.8,"www.missyusa.com":1}},{"modelVersion":"M_15","modelWeight":1,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":1.2,"missyusa.com":1.75}},{"modelVersion":"M_16","modelWeight":2,"schema":{"fields":["domain"]},"values":{"missyusa.com":1,"www.missyusa.com":0.7}},{"modelVersion":"M_17","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":0.45,"www.missyusa.com":0.35}},{"modelVersion":"M_18","modelWeight":3,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.2,"www.missyusa.com":1.05}}],"skipRate":10}` + return []byte(data) + }(), + responseStatus: 200, + want: nil, + want1: 0, + }, + { + name: "Malformed response : json unmarshalling failed", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + Timeout: 30, + MaxFileSizeKB: 800, + MaxRules: 30, + MaxAge: 60, + Period: 40, + }, + }, + response: func() []byte { + data := `{"data":nil?}` + return []byte(data) + }(), + responseStatus: 200, + want: nil, + want1: 0, + }, + { + name: "Validations failed for price floor rules response", + args: args{ + configs: config.AccountFloorFetch{ + Enabled: true, + Timeout: 30, + MaxFileSizeKB: 700, + MaxRules: 30, + MaxAge: 60, + Period: 40, + }, + }, + response: func() []byte { + data := `{"data":{"currency":"USD","modelgroups":[]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` + return []byte(data) + }(), + responseStatus: 200, + want: nil, + want1: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHttpServer := httptest.NewServer(mockHandler(tt.response, tt.responseStatus)) + defer mockHttpServer.Close() + ppf := PriceFloorFetcher{ + httpClient: mockHttpServer.Client(), + } + tt.args.configs.URL = mockHttpServer.URL + got, got1 := ppf.fetchAndValidate(tt.args.configs) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("fetchAndValidate() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("fetchAndValidate() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func mockFetcherInstance(config config.PriceFloors, httpClient *http.Client, metricEngine metrics.MetricsEngine) *PriceFloorFetcher { + if !config.Enabled { + return nil + } + + floorFetcher := PriceFloorFetcher{ + pool: pond.New(config.Fetcher.Worker, config.Fetcher.Capacity, pond.PanicHandler(workerPanicHandler)), + fetchQueue: make(FetchQueue, 0, 100), + fetchInProgress: make(map[string]bool), + configReceiver: make(chan fetchInfo, config.Fetcher.Capacity), + done: make(chan struct{}), + cache: freecache.NewCache(config.Fetcher.CacheSize * 1024 * 1024), + httpClient: httpClient, + time: &timeutil.RealTime{}, + metricEngine: metricEngine, + maxRetries: 10, + } + + go floorFetcher.Fetcher() + + return &floorFetcher +} + +func TestFetcherWhenRequestGetSameURLInrequest(t *testing.T) { + refetchCheckInterval = 1 + response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + mockHttpServer := httptest.NewServer(mockHandler(response, 200)) + defer mockHttpServer.Close() + + floorConfig := config.PriceFloors{ + Enabled: true, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 5, + Capacity: 10, + }, + } + fetcherInstance := mockFetcherInstance(floorConfig, mockHttpServer.Client(), &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: mockHttpServer.URL, + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 1, + }, + } + + for i := 0; i < 50; i++ { + fetcherInstance.Fetch(fetchConfig) + } + + assert.Never(t, func() bool { return len(fetcherInstance.fetchQueue) > 1 }, time.Duration(2*time.Second), 100*time.Millisecond, "Queue Got more than one entry") + assert.Never(t, func() bool { return len(fetcherInstance.fetchInProgress) > 1 }, time.Duration(2*time.Second), 100*time.Millisecond, "Map Got more than one entry") + +} + +func TestFetcherDataPresentInCache(t *testing.T) { + floorConfig := config.PriceFloors{ + Enabled: true, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 2, + Capacity: 5, + }, + } + + fetcherInstance := mockFetcherInstance(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: "http://test.com/floor", + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 5, + }, + } + var res *openrtb_ext.PriceFloorRules + data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` + _ = json.Unmarshal([]byte(data), &res) + fetcherInstance.SetWithExpiry("http://test.com/floor", []byte(data), fetchConfig.Fetcher.MaxAge) + + val, status := fetcherInstance.Fetch(fetchConfig) + assert.Equal(t, res, val, "Invalid value in cache or cache is empty") + assert.Equal(t, "success", status, "Floor fetch should be success") +} + +func TestFetcherDataNotPresentInCache(t *testing.T) { + floorConfig := config.PriceFloors{ + Enabled: true, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 2, + Capacity: 5, + }, + } + + fetcherInstance := mockFetcherInstance(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: "http://test.com/floor", + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 5, + }, + } + fetcherInstance.SetWithExpiry("http://test.com/floor", nil, fetchConfig.Fetcher.MaxAge) + + val, status := fetcherInstance.Fetch(fetchConfig) + + assert.Equal(t, (*openrtb_ext.PriceFloorRules)(nil), val, "Floor data should be nil") + assert.Equal(t, "error", status, "Floor fetch should be error") +} + +func TestFetcherEntryNotPresentInCache(t *testing.T) { + floorConfig := config.PriceFloors{ + Enabled: true, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 2, + Capacity: 5, + MaxRetries: 10, + }, + } + + fetcherInstance := NewPriceFloorFetcher(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: "http://test.com/floor", + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 5, + }, + } + + val, status := fetcherInstance.Fetch(fetchConfig) + + assert.Equal(t, (*openrtb_ext.PriceFloorRules)(nil), val, "Floor data should be nil") + assert.Equal(t, openrtb_ext.FetchInprogress, status, "Floor fetch should be error") +} + +func TestFetcherDynamicFetchDisable(t *testing.T) { + floorConfig := config.PriceFloors{ + Enabled: true, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 2, + Capacity: 5, + MaxRetries: 5, + }, + } + + fetcherInstance := NewPriceFloorFetcher(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: false, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: "http://test.com/floor", + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 5, + }, + } + + val, status := fetcherInstance.Fetch(fetchConfig) + + assert.Equal(t, (*openrtb_ext.PriceFloorRules)(nil), val, "Floor data should be nil") + assert.Equal(t, openrtb_ext.FetchNone, status, "Floor fetch should be error") +} + +func TestPriceFloorFetcherWorker(t *testing.T) { + var floorData openrtb_ext.PriceFloorData + response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) + _ = json.Unmarshal(response, &floorData) + floorResp := &openrtb_ext.PriceFloorRules{ + Data: &floorData, + } + + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add(MaxAge, "5") + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + mockHttpServer := httptest.NewServer(mockHandler(response, 200)) + defer mockHttpServer.Close() + + fetcherInstance := PriceFloorFetcher{ + pool: nil, + fetchQueue: nil, + fetchInProgress: nil, + configReceiver: make(chan fetchInfo, 1), + done: nil, + cache: freecache.NewCache(1 * 1024 * 1024), + httpClient: mockHttpServer.Client(), + time: &timeutil.RealTime{}, + metricEngine: &metricsConf.NilMetricsEngine{}, + maxRetries: 10, + } + defer close(fetcherInstance.configReceiver) + + fetchConfig := fetchInfo{ + AccountFloorFetch: config.AccountFloorFetch{ + Enabled: true, + URL: mockHttpServer.URL, + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 1, + }, + } + + fetcherInstance.worker(fetchConfig) + dataInCache, _ := fetcherInstance.Get(mockHttpServer.URL) + var gotFloorData *openrtb_ext.PriceFloorRules + json.Unmarshal(dataInCache, &gotFloorData) + assert.Equal(t, floorResp, gotFloorData, "Data should be stored in cache") + + info := <-fetcherInstance.configReceiver + assert.Equal(t, true, info.refetchRequest, "Recieved request is not refetch request") + assert.Equal(t, mockHttpServer.URL, info.AccountFloorFetch.URL, "Recieved request with different url") + +} + +func TestPriceFloorFetcherWorkerRetry(t *testing.T) { + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + mockHttpServer := httptest.NewServer(mockHandler(nil, 500)) + defer mockHttpServer.Close() + + fetcherInstance := PriceFloorFetcher{ + pool: nil, + fetchQueue: nil, + fetchInProgress: nil, + configReceiver: make(chan fetchInfo, 1), + done: nil, + cache: nil, + httpClient: mockHttpServer.Client(), + time: &timeutil.RealTime{}, + metricEngine: &metricsConf.NilMetricsEngine{}, + maxRetries: 5, + } + defer close(fetcherInstance.configReceiver) + + fetchConfig := fetchInfo{ + AccountFloorFetch: config.AccountFloorFetch{ + Enabled: true, + URL: mockHttpServer.URL, + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 1, + }, + } + + fetcherInstance.worker(fetchConfig) + + info := <-fetcherInstance.configReceiver + assert.Equal(t, 1, info.retryCount, "Retry Count is not 1") +} + +func TestPriceFloorFetcherWorkerDefaultCacheExpiry(t *testing.T) { + var floorData openrtb_ext.PriceFloorData + response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) + _ = json.Unmarshal(response, &floorData) + floorResp := &openrtb_ext.PriceFloorRules{ + Data: &floorData, + } + + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + mockHttpServer := httptest.NewServer(mockHandler(response, 200)) + defer mockHttpServer.Close() + + fetcherInstance := &PriceFloorFetcher{ + pool: nil, + fetchQueue: nil, + fetchInProgress: nil, + configReceiver: make(chan fetchInfo, 1), + done: nil, + cache: freecache.NewCache(1 * 1024 * 1024), + httpClient: mockHttpServer.Client(), + time: &timeutil.RealTime{}, + metricEngine: &metricsConf.NilMetricsEngine{}, + maxRetries: 5, + } + + fetchConfig := fetchInfo{ + AccountFloorFetch: config.AccountFloorFetch{ + Enabled: true, + URL: mockHttpServer.URL, + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 20, + Period: 1, + }, + } + + fetcherInstance.worker(fetchConfig) + dataInCache, _ := fetcherInstance.Get(mockHttpServer.URL) + var gotFloorData *openrtb_ext.PriceFloorRules + json.Unmarshal(dataInCache, &gotFloorData) + assert.Equal(t, floorResp, gotFloorData, "Data should be stored in cache") + + info := <-fetcherInstance.configReceiver + defer close(fetcherInstance.configReceiver) + assert.Equal(t, true, info.refetchRequest, "Recieved request is not refetch request") + assert.Equal(t, mockHttpServer.URL, info.AccountFloorFetch.URL, "Recieved request with different url") + +} + +func TestPriceFloorFetcherSubmit(t *testing.T) { + response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + mockHttpServer := httptest.NewServer(mockHandler(response, 200)) + defer mockHttpServer.Close() + + fetcherInstance := &PriceFloorFetcher{ + pool: pond.New(1, 1), + fetchQueue: make(FetchQueue, 0), + fetchInProgress: nil, + configReceiver: make(chan fetchInfo, 1), + done: make(chan struct{}), + cache: freecache.NewCache(1 * 1024 * 1024), + httpClient: mockHttpServer.Client(), + time: &timeutil.RealTime{}, + metricEngine: &metricsConf.NilMetricsEngine{}, + maxRetries: 5, + } + defer fetcherInstance.Stop() + + fetchInfo := fetchInfo{ + refetchRequest: false, + fetchTime: time.Now().Unix(), + AccountFloorFetch: config.AccountFloorFetch{ + Enabled: true, + URL: mockHttpServer.URL, + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 2, + Period: 1, + }, + } + + fetcherInstance.submit(&fetchInfo) + + info := <-fetcherInstance.configReceiver + assert.Equal(t, true, info.refetchRequest, "Recieved request is not refetch request") + assert.Equal(t, mockHttpServer.URL, info.AccountFloorFetch.URL, "Recieved request with different url") + +} + +type testPool struct{} + +func (t *testPool) TrySubmit(task func()) bool { + return false +} + +func (t *testPool) Stop() {} + +func TestPriceFloorFetcherSubmitFailed(t *testing.T) { + fetcherInstance := &PriceFloorFetcher{ + pool: &testPool{}, + fetchQueue: make(FetchQueue, 0), + fetchInProgress: nil, + configReceiver: nil, + done: nil, + cache: nil, + } + defer fetcherInstance.pool.Stop() + + fetchInfo := fetchInfo{ + refetchRequest: false, + fetchTime: time.Now().Unix(), + AccountFloorFetch: config.AccountFloorFetch{ + Enabled: true, + URL: "http://test.com", + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 2, + Period: 1, + }, + } + + fetcherInstance.submit(&fetchInfo) + assert.Equal(t, 1, len(fetcherInstance.fetchQueue), "Unable to submit the task") +} + +func getRandomNumber() int { + //nolint: staticcheck // SA1019: rand.Seed has been deprecated since Go 1.20 and an alternative has been available since Go 1.0: As of Go 1.20 there is no reason to call Seed with a random value. + rand.Seed(time.Now().UnixNano()) + min := 1 + max := 10 + return rand.Intn(max-min+1) + min +} + +func TestFetcherWhenRequestGetDifferentURLInrequest(t *testing.T) { + refetchCheckInterval = 1 + response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) + mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(mockStatus) + w.Write(mockResponse) + }) + } + + mockHttpServer := httptest.NewServer(mockHandler(response, 200)) + defer mockHttpServer.Close() + + floorConfig := config.PriceFloors{ + Enabled: true, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 5, + Capacity: 10, + MaxRetries: 5, + }, + } + fetcherInstance := mockFetcherInstance(floorConfig, mockHttpServer.Client(), &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: mockHttpServer.URL, + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 5, + Period: 1, + }, + } + + for i := 0; i < 50; i++ { + fetchConfig.Fetcher.URL = fmt.Sprintf("%s?id=%d", mockHttpServer.URL, getRandomNumber()) + fetcherInstance.Fetch(fetchConfig) + } + + assert.Never(t, func() bool { return len(fetcherInstance.fetchQueue) > 10 }, time.Duration(2*time.Second), 100*time.Millisecond, "Queue Got more than one entry") + assert.Never(t, func() bool { return len(fetcherInstance.fetchInProgress) > 10 }, time.Duration(2*time.Second), 100*time.Millisecond, "Map Got more than one entry") +} + +func TestFetchWhenPriceFloorsDisabled(t *testing.T) { + floorConfig := config.PriceFloors{ + Enabled: false, + Fetcher: config.PriceFloorFetcher{ + CacheSize: 1, + Worker: 5, + Capacity: 10, + }, + } + fetcherInstance := NewPriceFloorFetcher(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) + defer fetcherInstance.Stop() + + fetchConfig := config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + Fetcher: config.AccountFloorFetch{ + Enabled: true, + URL: "http://test.com/floors", + Timeout: 100, + MaxFileSizeKB: 1000, + MaxRules: 100, + MaxAge: 5, + Period: 1, + }, + } + + data, status := fetcherInstance.Fetch(fetchConfig) + + assert.Equal(t, (*openrtb_ext.PriceFloorRules)(nil), data, "floor data should be nil as fetcher instance does not created") + assert.Equal(t, openrtb_ext.FetchNone, status, "floor status should be none as fetcher instance does not created") +} diff --git a/floors/floors.go b/floors/floors.go index 52591915058..5bcd2e5e62c 100644 --- a/floors/floors.go +++ b/floors/floors.go @@ -4,10 +4,12 @@ import ( "errors" "math" "math/rand" + "strings" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/ptrutil" ) type Price struct { @@ -26,12 +28,13 @@ const ( enforceRateMin int = 0 enforceRateMax int = 100 floorPrecision float64 = 0.01 + dataRateMin int = 0 + dataRateMax int = 100 ) // 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) []error { - +func EnrichWithPriceFloors(bidRequestWrapper *openrtb_ext.RequestWrapper, account config.Account, conversions currency.Conversions, priceFloorFetcher FloorFetcher) []error { if bidRequestWrapper == nil || bidRequestWrapper.BidRequest == nil { return []error{errors.New("Empty bidrequest")} } @@ -40,7 +43,7 @@ func EnrichWithPriceFloors(bidRequestWrapper *openrtb_ext.RequestWrapper, accoun return []error{errors.New("Floors feature is disabled at account or in the request")} } - floors, err := resolveFloors(account, bidRequestWrapper, conversions) + floors, err := resolveFloors(account, bidRequestWrapper, conversions, priceFloorFetcher) updateReqErrs := updateBidRequestWithFloors(floors, bidRequestWrapper, conversions) updateFloorsInRequest(bidRequestWrapper, floors) @@ -135,13 +138,38 @@ func isPriceFloorsEnabledForRequest(bidRequestWrapper *openrtb_ext.RequestWrappe return true } +// useFetchedData will check if to use fetched data or request data +func useFetchedData(rate *int) bool { + if rate == nil { + return true + } + randomNumber := rand.Intn(dataRateMax) + return randomNumber < *rate +} + // 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) (*openrtb_ext.PriceFloorRules, []error) { - var errList []error - var floorRules *openrtb_ext.PriceFloorRules +func resolveFloors(account config.Account, bidRequestWrapper *openrtb_ext.RequestWrapper, conversions currency.Conversions, priceFloorFetcher FloorFetcher) (*openrtb_ext.PriceFloorRules, []error) { + var ( + errList []error + floorRules *openrtb_ext.PriceFloorRules + fetchResult *openrtb_ext.PriceFloorRules + fetchStatus string + ) reqFloor := extractFloorsFromRequest(bidRequestWrapper) - if reqFloor != nil { + if reqFloor != nil && reqFloor.Location != nil && len(reqFloor.Location.URL) > 0 { + account.PriceFloors.Fetcher.URL = reqFloor.Location.URL + } + account.PriceFloors.Fetcher.AccountID = account.ID + + if priceFloorFetcher != nil && account.PriceFloors.UseDynamicData { + fetchResult, fetchStatus = priceFloorFetcher.Fetch(account.PriceFloors) + } + + if fetchResult != nil && fetchStatus == openrtb_ext.FetchSuccess && useFetchedData(fetchResult.Data.UseFetchDataRate) { + mergedFloor := mergeFloors(reqFloor, fetchResult, conversions) + floorRules, errList = createFloorsFrom(mergedFloor, account, fetchStatus, openrtb_ext.FetchLocation) + } else if reqFloor != nil { floorRules, errList = createFloorsFrom(reqFloor, account, openrtb_ext.FetchNone, openrtb_ext.RequestLocation) } else { floorRules, errList = createFloorsFrom(nil, account, openrtb_ext.FetchNone, openrtb_ext.NoDataLocation) @@ -210,3 +238,83 @@ func updateFloorsInRequest(bidRequestWrapper *openrtb_ext.RequestWrapper, priceF bidRequestWrapper.RebuildRequest() } } + +// resolveFloorMin gets floorMin value from request and dynamic fetched data +func resolveFloorMin(reqFloors *openrtb_ext.PriceFloorRules, fetchFloors *openrtb_ext.PriceFloorRules, conversions currency.Conversions) Price { + var requestFloorMinCur, providerFloorMinCur string + var requestFloorMin, providerFloorMin float64 + + if reqFloors != nil { + requestFloorMin = reqFloors.FloorMin + requestFloorMinCur = reqFloors.FloorMinCur + if len(requestFloorMinCur) == 0 && reqFloors.Data != nil { + requestFloorMinCur = reqFloors.Data.Currency + } + } + + if fetchFloors != nil { + providerFloorMin = fetchFloors.FloorMin + providerFloorMinCur = fetchFloors.FloorMinCur + if len(providerFloorMinCur) == 0 && fetchFloors.Data != nil { + providerFloorMinCur = fetchFloors.Data.Currency + } + } + + if len(requestFloorMinCur) > 0 { + if requestFloorMin > 0 { + return Price{FloorMin: requestFloorMin, FloorMinCur: requestFloorMinCur} + } + + if providerFloorMin > 0 { + if strings.Compare(providerFloorMinCur, requestFloorMinCur) == 0 || len(providerFloorMinCur) == 0 { + return Price{FloorMin: providerFloorMin, FloorMinCur: requestFloorMinCur} + } + rate, err := conversions.GetRate(providerFloorMinCur, requestFloorMinCur) + if err != nil { + return Price{FloorMin: 0, FloorMinCur: requestFloorMinCur} + } + return Price{FloorMin: roundToFourDecimals(rate * providerFloorMin), FloorMinCur: requestFloorMinCur} + } + } + + if len(providerFloorMinCur) > 0 { + if providerFloorMin > 0 { + return Price{FloorMin: providerFloorMin, FloorMinCur: providerFloorMinCur} + } + if requestFloorMin > 0 { + return Price{FloorMin: requestFloorMin, FloorMinCur: providerFloorMinCur} + } + } + + return Price{FloorMin: requestFloorMin, FloorMinCur: requestFloorMinCur} + +} + +// mergeFloors does merging for floors data from request and dynamic fetch +func mergeFloors(reqFloors *openrtb_ext.PriceFloorRules, fetchFloors *openrtb_ext.PriceFloorRules, conversions currency.Conversions) *openrtb_ext.PriceFloorRules { + mergedFloors := fetchFloors.DeepCopy() + if mergedFloors.Enabled == nil { + mergedFloors.Enabled = new(bool) + } + *mergedFloors.Enabled = fetchFloors.GetEnabled() && reqFloors.GetEnabled() + + if reqFloors == nil { + return mergedFloors + } + + if reqFloors.Enforcement != nil { + mergedFloors.Enforcement = reqFloors.Enforcement.DeepCopy() + } + + floorMinPrice := resolveFloorMin(reqFloors, fetchFloors, conversions) + if floorMinPrice.FloorMin > 0 { + mergedFloors.FloorMin = floorMinPrice.FloorMin + mergedFloors.FloorMinCur = floorMinPrice.FloorMinCur + } + + if reqFloors != nil && reqFloors.Location != nil && reqFloors.Location.URL != "" { + mergedFloors.Location = ptrutil.Clone(reqFloors.Location) + } + + return mergedFloors +} diff --git a/floors/floors_test.go b/floors/floors_test.go index b3002c2f155..8d73a5de0ae 100644 --- a/floors/floors_test.go +++ b/floors/floors_test.go @@ -3,13 +3,15 @@ package floors import ( "encoding/json" "errors" + "reflect" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -50,6 +52,14 @@ func getCurrencyRates(rates map[string]map[string]float64) currency.Conversions return currency.NewRates(rates) } +type mockPriceFloorFetcher struct{} + +func (mpf *mockPriceFloorFetcher) Fetch(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) { + return nil, openrtb_ext.FetchNone +} + +func (mpf *mockPriceFloorFetcher) Stop() {} + func TestEnrichWithPriceFloors(t *testing.T) { rates := map[string]map[string]float64{ "USD": { @@ -81,7 +91,7 @@ func TestEnrichWithPriceFloors(t *testing.T) { expFloorVal float64 expFloorCur string expPriceFlrLoc string - expSchemaVersion string + expSchemaVersion int }{ { name: "Floors disabled in account config", @@ -168,7 +178,7 @@ func TestEnrichWithPriceFloors(t *testing.T) { 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":{"floormin":11,"floormincur":"USD","data":{"currency":"USD","floorsschemaversion":"2","modelgroups":[{"modelweight":50,"modelversion":"version2","schema":{"fields":["mediaType","size","domain"],"delimiter":"|"},"values":{"*|*|*":11.01,"*|*|www.website1.com":17.01},"default":21},{"modelweight":50,"modelversion":"version11","skiprate":110,"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"},"values":{"*|300x250|*":11.01,"*|300x250|www.website1.com":100.01},"default":21}]},"enforcement":{"enforcepbs":true,"floordeals":true},"enabled":true}}}`), + Ext: json.RawMessage(`{"prebid":{"floors":{"floormin":11,"floormincur":"USD","data":{"currency":"USD","floorsschemaversion":2,"modelgroups":[{"modelweight":50,"modelversion":"version2","schema":{"fields":["mediaType","size","domain"],"delimiter":"|"},"values":{"*|*|*":11.01,"*|*|www.website1.com":17.01},"default":21},{"modelweight":50,"modelversion":"version11","skiprate":110,"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"},"values":{"*|300x250|*":11.01,"*|300x250|www.website1.com":100.01},"default":21}]},"enforcement":{"enforcepbs":true,"floordeals":true},"enabled":true}}}`), }, }, account: testAccountConfig, @@ -176,7 +186,7 @@ func TestEnrichWithPriceFloors(t *testing.T) { expFloorVal: 11.01, expFloorCur: "USD", expPriceFlrLoc: openrtb_ext.RequestLocation, - expSchemaVersion: "2", + expSchemaVersion: 2, }, { name: "Rule selection with Site object, banner|300x600|www.website.com", @@ -365,7 +375,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)) + ErrList := EnrichWithPriceFloors(tc.bidRequestWrapper, tc.account, getCurrencyRates(rates), &mockPriceFloorFetcher{}) 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) @@ -375,7 +385,7 @@ func TestEnrichWithPriceFloors(t *testing.T) { assert.Equal(t, *requestExt.GetPrebid().Floors.Skipped, tc.Skipped, tc.name) } else { assert.Equal(t, requestExt.GetPrebid().Floors.PriceFloorLocation, tc.expPriceFlrLoc, tc.name) - if tc.expSchemaVersion != "" { + if tc.expSchemaVersion != 0 { assert.Equal(t, requestExt.GetPrebid().Floors.Data.FloorsSchemaVersion, tc.expSchemaVersion, tc.name) } } @@ -393,6 +403,51 @@ func getTrue() *bool { return &trueFlag } +func getFalse() *bool { + falseFlag := false + return &falseFlag +} + +type MockFetch struct { + FakeFetch func(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) +} + +func (m *MockFetch) Stop() {} + +func (m *MockFetch) Fetch(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) { + + if !configs.UseDynamicData { + return nil, openrtb_ext.FetchNone + } + priceFloors := openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + 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: "Version 101", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 15, + "*|*|*": 25, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + } + return &priceFloors, openrtb_ext.FetchSuccess +} + func TestResolveFloors(t *testing.T) { rates := map[string]map[string]float64{ "USD": { @@ -407,18 +462,19 @@ func TestResolveFloors(t *testing.T) { bidRequestWrapper *openrtb_ext.RequestWrapper account config.Account conversions currency.Conversions + fetcher FloorFetcher expErr []error expFloors *openrtb_ext.PriceFloorRules }{ { - name: "Dynamic fetch disabled, only enforcement object present in req.ext", + name: "Dynamic fetch disabled, floors from request selected", bidRequestWrapper: &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":{"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`), + Ext: json.RawMessage(`{"prebid":{"floors":{"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x600|www.website5.com":5,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`), }, }, account: config.Account{ @@ -427,153 +483,99 @@ func TestResolveFloors(t *testing.T) { UseDynamicData: false, }, }, + fetcher: &MockFetch{}, expFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FetchStatus: openrtb_ext.FetchNone, + PriceFloorLocation: openrtb_ext.RequestLocation, Enforcement: &openrtb_ext.PriceFloorEnforcement{ EnforcePBS: getTrue(), EnforceRate: 100, FloorDeals: getTrue(), }, - FetchStatus: openrtb_ext.FetchNone, - PriceFloorLocation: openrtb_ext.RequestLocation, + 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: "|", + }, + }, + }, + }, }, }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - resolvedFloors, _ := resolveFloors(tc.account, tc.bidRequestWrapper, getCurrencyRates(rates)) - assert.Equal(t, resolvedFloors, tc.expFloors, tc.name) - }) - } -} - -func printFloors(floors *openrtb_ext.PriceFloorRules) string { - fbytes, _ := jsonutil.Marshal(floors) - return string(fbytes) -} - -func TestCreateFloorsFrom(t *testing.T) { - - testAccountConfig := config.Account{ - PriceFloors: config.AccountPriceFloors{ - Enabled: true, - UseDynamicData: false, - MaxRule: 100, - MaxSchemaDims: 5, - }, - } - - type args struct { - floors *openrtb_ext.PriceFloorRules - account config.Account - fetchStatus string - floorLocation string - } - testCases := []struct { - name string - args args - want *openrtb_ext.PriceFloorRules - want1 []error - }{ { - name: "floor provider should be selected from floor json", - args: args{ - account: testAccountConfig, - floors: &openrtb_ext.PriceFloorRules{ - Enabled: getTrue(), - FloorMin: 10.11, - FloorMinCur: "EUR", - Enforcement: &openrtb_ext.PriceFloorEnforcement{ - EnforcePBS: getTrue(), - EnforceRate: 100, - FloorDeals: 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"}, - }, - }, - }, - FloorProvider: "PM", + name: "Dynamic fetch enabled, floors from fetched selected", + bidRequestWrapper: &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}}}}}, }, - fetchStatus: openrtb_ext.FetchSuccess, - floorLocation: openrtb_ext.FetchLocation, }, - want: &openrtb_ext.PriceFloorRules{ + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + fetcher: &MockFetch{}, + expFloors: &openrtb_ext.PriceFloorRules{ Enabled: getTrue(), - FloorMin: 10.11, - FloorMinCur: "EUR", FetchStatus: openrtb_ext.FetchSuccess, PriceFloorLocation: openrtb_ext.FetchLocation, Enforcement: &openrtb_ext.PriceFloorEnforcement{ EnforcePBS: getTrue(), - EnforceRate: 100, FloorDeals: getTrue(), + EnforceRate: 100, }, Data: &openrtb_ext.PriceFloorData{ Currency: "USD", ModelGroups: []openrtb_ext.PriceFloorModelGroup{ { - ModelVersion: "model from fetched", + ModelVersion: "Version 101", Currency: "USD", Values: map[string]float64{ "banner|300x600|www.website5.com": 15, "*|*|*": 25, }, Schema: openrtb_ext.PriceFloorSchema{ - Fields: []string{"mediaType", "size", "domain"}, + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", }, }, }, - FloorProvider: "PM", }, }, }, { - name: "floor provider will be empty if no value provided in floor json", - args: args{ - account: testAccountConfig, - floors: &openrtb_ext.PriceFloorRules{ - Enabled: getTrue(), - FloorMin: 10.11, - FloorMinCur: "EUR", - Enforcement: &openrtb_ext.PriceFloorEnforcement{ - EnforcePBS: getTrue(), - EnforceRate: 100, - FloorDeals: getTrue(), - }, - Data: &openrtb_ext.PriceFloorData{ - Currency: "USD", - ModelGroups: []openrtb_ext.PriceFloorModelGroup{ - { - ModelVersion: "model from request", - Currency: "USD", - Values: map[string]float64{ - "banner|300x600|www.website5.com": 15, - "*|*|*": 25, - }, - Schema: openrtb_ext.PriceFloorSchema{ - Fields: []string{"mediaType", "size", "domain"}, - }, - }, - }, - FloorProvider: "", + name: "Dynamic fetch enabled, floors formed after merging", + bidRequestWrapper: &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":{"floormincur":"EUR","enabled":true,"data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","currency":"USD","values":{"banner|300x600|www.website5.com":5,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"floormin":10.11,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`), }, - fetchStatus: openrtb_ext.FetchSuccess, - floorLocation: openrtb_ext.FetchLocation, }, - want: &openrtb_ext.PriceFloorRules{ + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + fetcher: &MockFetch{}, + expFloors: &openrtb_ext.PriceFloorRules{ Enabled: getTrue(), FloorMin: 10.11, FloorMinCur: "EUR", @@ -588,74 +590,663 @@ func TestCreateFloorsFrom(t *testing.T) { Currency: "USD", ModelGroups: []openrtb_ext.PriceFloorModelGroup{ { - ModelVersion: "model from request", + ModelVersion: "Version 101", Currency: "USD", Values: map[string]float64{ "banner|300x600|www.website5.com": 15, "*|*|*": 25, }, Schema: openrtb_ext.PriceFloorSchema{ - Fields: []string{"mediaType", "size", "domain"}, + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", }, }, }, - FloorProvider: "", }, }, }, { - name: "only floor enforcement object present", - args: args{ - account: testAccountConfig, - floors: &openrtb_ext.PriceFloorRules{ - Enabled: getTrue(), - Enforcement: &openrtb_ext.PriceFloorEnforcement{ - EnforcePBS: getTrue(), - EnforceRate: 100, - FloorDeals: getTrue(), + name: "Dynamic fetch disabled, only enforcement object present in req.ext", + bidRequestWrapper: &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":{"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`), }, - fetchStatus: openrtb_ext.FetchNone, - floorLocation: openrtb_ext.RequestLocation, }, - want: &openrtb_ext.PriceFloorRules{ - FetchStatus: openrtb_ext.FetchNone, - PriceFloorLocation: openrtb_ext.RequestLocation, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: false, + }, + }, + fetcher: &MockFetch{}, + expFloors: &openrtb_ext.PriceFloorRules{ Enforcement: &openrtb_ext.PriceFloorEnforcement{ EnforcePBS: getTrue(), EnforceRate: 100, FloorDeals: getTrue(), }, + FetchStatus: openrtb_ext.FetchNone, + PriceFloorLocation: openrtb_ext.RequestLocation, }, }, { - name: "Invalid modelGroup with skipRate = 110", - args: args{ - account: testAccountConfig, - floors: &openrtb_ext.PriceFloorRules{ - Enabled: getTrue(), - Data: &openrtb_ext.PriceFloorData{ - Currency: "USD", - ModelGroups: []openrtb_ext.PriceFloorModelGroup{ - { - ModelVersion: "model from fetched", - Currency: "USD", - SkipRate: 110, - Values: map[string]float64{ - "banner|300x600|www.website5.com": 15, - "*|*|*": 25, - }, - Schema: openrtb_ext.PriceFloorSchema{ - Fields: []string{"mediaType", "size", "domain"}, - }, + name: "Dynamic fetch enabled, floors from fetched selected and new URL is updated", + bidRequestWrapper: &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":{"floorendpoint":{"url":"http://test.com/floor"},"enabled":true}}}`), + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + fetcher: &MockFetch{}, + expFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FetchStatus: openrtb_ext.FetchSuccess, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + FloorDeals: getTrue(), + EnforceRate: 100, + }, + Location: &openrtb_ext.PriceFloorEndpoint{ + URL: "http://test.com/floor", + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 101", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 15, + "*|*|*": 25, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", }, }, }, }, - fetchStatus: openrtb_ext.FetchNone, - floorLocation: openrtb_ext.RequestLocation, }, - want: &openrtb_ext.PriceFloorRules{ + }, + { + name: "Dynamic Fetch Enabled but price floor fetcher is nil, floors from request is selected", + bidRequestWrapper: &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,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`), + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + fetcher: nil, + expFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FetchStatus: openrtb_ext.FetchNone, + 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: "|", + }, + }, + }, + }, + }, + }, + { + name: "Dynamic Fetch Enabled but price floor fetcher is nil and request has no floors", + bidRequestWrapper: &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":{}}`), + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + fetcher: nil, + expFloors: &openrtb_ext.PriceFloorRules{ + FetchStatus: openrtb_ext.FetchNone, + PriceFloorLocation: openrtb_ext.NoDataLocation, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + resolvedFloors, _ := resolveFloors(tc.account, tc.bidRequestWrapper, getCurrencyRates(rates), tc.fetcher) + assert.Equal(t, resolvedFloors, tc.expFloors, tc.name) + }) + } +} + +type MockFetchDataRate0 struct{} + +func (m *MockFetchDataRate0) Fetch(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) { + + if !configs.UseDynamicData { + return nil, openrtb_ext.FetchNone + } + priceFloors := openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + 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 from fetched", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 15, + "*|*|*": 25, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + }, + }, + }, + UseFetchDataRate: ptrutil.ToPtr(0), + }, + } + return &priceFloors, openrtb_ext.FetchSuccess +} + +func (m *MockFetchDataRate0) Stop() { + +} + +type MockFetchDataRate100 struct{} + +func (m *MockFetchDataRate100) Fetch(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) { + + if !configs.UseDynamicData { + return nil, openrtb_ext.FetchNone + } + priceFloors := openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + 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 from fetched", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 15, + "*|*|*": 25, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + }, + }, + }, + UseFetchDataRate: ptrutil.ToPtr(100), + }, + } + return &priceFloors, openrtb_ext.FetchSuccess +} + +func (m *MockFetchDataRate100) Stop() { + +} + +type MockFetchDataRateNotProvided struct{} + +func (m *MockFetchDataRateNotProvided) Fetch(configs config.AccountPriceFloors) (*openrtb_ext.PriceFloorRules, string) { + + if !configs.UseDynamicData { + return nil, openrtb_ext.FetchNone + } + priceFloors := openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + 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 from fetched", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 5, + "*|*|*": 15, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + }, + }, + }, + }, + } + return &priceFloors, openrtb_ext.FetchSuccess +} + +func (m *MockFetchDataRateNotProvided) Stop() { + +} + +func TestResolveFloorsWithUseDataRate(t *testing.T) { + rates := map[string]map[string]float64{} + + testCases := []struct { + name string + bidRequestWrapper *openrtb_ext.RequestWrapper + account config.Account + conversions currency.Conversions + expErr []error + expFloors *openrtb_ext.PriceFloorRules + fetcher FloorFetcher + }{ + { + name: "Dynamic fetch enabled, floors from request selected as data rate 0", + fetcher: &MockFetchDataRate0{}, + bidRequestWrapper: &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,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`), + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + expFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FetchStatus: openrtb_ext.FetchNone, + PriceFloorLocation: openrtb_ext.RequestLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + FloorDeals: getTrue(), + EnforceRate: 100, + }, + 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: "|", + }, + }, + }, + }, + }, + }, + { + name: "Dynamic fetch enabled, floors from fetched selected as data rate is 100", + fetcher: &MockFetchDataRate100{}, + bidRequestWrapper: &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}}}}}, + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + expFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FetchStatus: openrtb_ext.FetchSuccess, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + FloorDeals: getTrue(), + EnforceRate: 100, + }, + 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"}, + }, + }, + }, + UseFetchDataRate: ptrutil.ToPtr(100), + }, + }, + }, + { + name: "Dynamic fetch enabled, floors from fetched selected as data rate not provided as default value = 100", + fetcher: &MockFetchDataRateNotProvided{}, + bidRequestWrapper: &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,"*|*|*":7},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"enforcement":{"enforcepbs":true,"floordeals":true,"enforcerate":100}}}}`), + }, + }, + account: config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: true, + }, + }, + expFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FetchStatus: openrtb_ext.FetchSuccess, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + FloorDeals: getTrue(), + EnforceRate: 100, + }, + 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": 5, + "*|*|*": 15, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + }, + }, + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + resolvedFloors, _ := resolveFloors(tc.account, tc.bidRequestWrapper, getCurrencyRates(rates), tc.fetcher) + assert.Equal(t, resolvedFloors, tc.expFloors, tc.name) + }) + } +} + +func printFloors(floors *openrtb_ext.PriceFloorRules) string { + fbytes, _ := jsonutil.Marshal(floors) + return string(fbytes) +} + +func TestCreateFloorsFrom(t *testing.T) { + + testAccountConfig := config.Account{ + PriceFloors: config.AccountPriceFloors{ + Enabled: true, + UseDynamicData: false, + MaxRule: 100, + MaxSchemaDims: 5, + }, + } + + type args struct { + floors *openrtb_ext.PriceFloorRules + account config.Account + fetchStatus string + floorLocation string + } + testCases := []struct { + name string + args args + want *openrtb_ext.PriceFloorRules + want1 []error + }{ + { + name: "floor provider should be selected from floor json", + args: args{ + account: testAccountConfig, + floors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FloorMin: 10.11, + FloorMinCur: "EUR", + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: 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"}, + }, + }, + }, + FloorProvider: "PM", + }, + }, + fetchStatus: openrtb_ext.FetchSuccess, + floorLocation: openrtb_ext.FetchLocation, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FloorMin: 10.11, + FloorMinCur: "EUR", + FetchStatus: openrtb_ext.FetchSuccess, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: 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"}, + }, + }, + }, + FloorProvider: "PM", + }, + }, + }, + { + name: "floor provider will be empty if no value provided in floor json", + args: args{ + account: testAccountConfig, + floors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FloorMin: 10.11, + FloorMinCur: "EUR", + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: getTrue(), + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "model from request", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 15, + "*|*|*": 25, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + }, + }, + }, + FloorProvider: "", + }, + }, + fetchStatus: openrtb_ext.FetchSuccess, + floorLocation: openrtb_ext.FetchLocation, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + FloorMin: 10.11, + FloorMinCur: "EUR", + FetchStatus: openrtb_ext.FetchSuccess, + PriceFloorLocation: openrtb_ext.FetchLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: getTrue(), + }, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "model from request", + Currency: "USD", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 15, + "*|*|*": 25, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + }, + }, + }, + FloorProvider: "", + }, + }, + }, + { + name: "only floor enforcement object present", + args: args{ + account: testAccountConfig, + floors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: getTrue(), + }, + }, + fetchStatus: openrtb_ext.FetchNone, + floorLocation: openrtb_ext.RequestLocation, + }, + want: &openrtb_ext.PriceFloorRules{ + FetchStatus: openrtb_ext.FetchNone, + PriceFloorLocation: openrtb_ext.RequestLocation, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforcePBS: getTrue(), + EnforceRate: 100, + FloorDeals: getTrue(), + }, + }, + }, + { + name: "Invalid modelGroup with skipRate = 110", + args: args{ + account: testAccountConfig, + floors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "model from fetched", + Currency: "USD", + SkipRate: 110, + 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, }, @@ -664,77 +1255,679 @@ func TestCreateFloorsFrom(t *testing.T) { }, }, } - for _, tc := range testCases { + 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) + assert.Equal(t, got1, tc.want1, tc.name) + assert.Equal(t, got, tc.want, tc.name) + }) + } +} + +func TestIsPriceFloorsEnabled(t *testing.T) { + type args struct { + account config.Account + bidRequestWrapper *openrtb_ext.RequestWrapper + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "Disabled in account and req", + args: args{ + account: config.Account{PriceFloors: config.AccountPriceFloors{Enabled: false}}, + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"floors":{"enabled": false} }}`), + }, + }, + }, + want: false, + }, + { + name: "Enabled in account and req", + args: args{ + account: config.Account{PriceFloors: config.AccountPriceFloors{Enabled: true}}, + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"floors":{"enabled": true} }}`), + }, + }, + }, + want: true, + }, + { + name: "disabled in account and enabled req", + args: args{ + account: config.Account{PriceFloors: config.AccountPriceFloors{Enabled: false}}, + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"floors":{"enabled": true} }}`), + }, + }, + }, + want: false, + }, + { + name: "Enabled in account and disabled in req", + args: args{ + account: config.Account{PriceFloors: config.AccountPriceFloors{Enabled: true}}, + bidRequestWrapper: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"floors":{"enabled": false} }}`)}, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isPriceFloorsEnabled(tt.args.account, tt.args.bidRequestWrapper) + assert.Equal(t, got, tt.want, tt.name) + }) + } +} + +func TestResolveFloorMin(t *testing.T) { + rates := map[string]map[string]float64{ + "USD": { + "INR": 70, + "EUR": 0.9, + "JPY": 5.09, + }, + } + + tt := []struct { + name string + reqFloors openrtb_ext.PriceFloorRules + fetchFloors openrtb_ext.PriceFloorRules + conversions currency.Conversions + expPrice Price + }{ + { + name: "FloorsMin present in request Floors only", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 10, + FloorMinCur: "JPY", + }, + fetchFloors: openrtb_ext.PriceFloorRules{}, + expPrice: Price{FloorMin: 10, FloorMinCur: "JPY"}, + }, + { + name: "FloorsMin, FloorMinCur and data currency present in request Floors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 10, + FloorMinCur: "JPY", + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + }, + }, + fetchFloors: openrtb_ext.PriceFloorRules{}, + expPrice: Price{FloorMin: 10, FloorMinCur: "JPY"}, + }, + { + name: "FloorsMin and data currency present in request Floors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 10, + Data: &openrtb_ext.PriceFloorData{ + Currency: "USD", + }, + }, + fetchFloors: openrtb_ext.PriceFloorRules{}, + expPrice: Price{FloorMin: 10, FloorMinCur: "USD"}, + }, + { + name: "FloorsMin and FloorMinCur present in request Floors and fetched floors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 10, + FloorMinCur: "USD", + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 15, + FloorMinCur: "INR", + }, + expPrice: Price{FloorMin: 10, FloorMinCur: "USD"}, + }, + { + name: "FloorsMin present fetched floors only", + reqFloors: openrtb_ext.PriceFloorRules{}, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 15, + FloorMinCur: "EUR", + }, + expPrice: Price{FloorMin: 15, FloorMinCur: "EUR"}, + }, + { + name: "FloorMinCur present in reqFloors And FloorsMin, FloorMinCur present in fetched floors (Same Currency)", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMinCur: "EUR", + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 15, + FloorMinCur: "EUR", + }, + expPrice: Price{FloorMin: 15, FloorMinCur: "EUR"}, + }, + { + name: "FloorMinCur present in reqFloors And FloorsMin, FloorMinCur present in fetched floors (Different Currency)", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMinCur: "USD", + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 15, + FloorMinCur: "EUR", + }, + expPrice: Price{FloorMin: 16.6667, FloorMinCur: "USD"}, + }, + { + name: "FloorMin present in reqFloors And FloorMinCur present in fetched floors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 11, + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMinCur: "EUR", + }, + expPrice: Price{FloorMin: 11, FloorMinCur: "EUR"}, + }, + { + name: "FloorMinCur present in reqFloors And FloorMin present in fetched floors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMinCur: "INR", + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 12, + }, + expPrice: Price{FloorMin: 12, FloorMinCur: "INR"}, + }, + { + name: "FloorMinCur present in reqFloors And FloorMin, data currency present in fetched floors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMinCur: "INR", + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 1, + Data: &openrtb_ext.PriceFloorData{Currency: "USD"}, + }, + expPrice: Price{FloorMin: 70, FloorMinCur: "INR"}, + }, + { + name: "FloorMinCur present in fetched Floors And data currency present in reqFloors", + reqFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 2, + }, + fetchFloors: openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{Currency: "USD"}, + }, + expPrice: Price{FloorMin: 2, FloorMinCur: "USD"}, + }, + { + name: "Data currency and FloorMin present in fetched floors", + reqFloors: openrtb_ext.PriceFloorRules{}, + fetchFloors: openrtb_ext.PriceFloorRules{ + FloorMin: 12, + Data: &openrtb_ext.PriceFloorData{Currency: "USD"}, + }, + expPrice: Price{FloorMin: 12, FloorMinCur: "USD"}, + }, + { + name: "Empty reqFloors And Empty fetched floors", + reqFloors: openrtb_ext.PriceFloorRules{}, + fetchFloors: openrtb_ext.PriceFloorRules{}, + expPrice: Price{FloorMin: 0.0, FloorMinCur: ""}, + }, + } + for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { - got, got1 := createFloorsFrom(tc.args.floors, tc.args.account, tc.args.fetchStatus, tc.args.floorLocation) - assert.Equal(t, got1, tc.want1, tc.name) - assert.Equal(t, got, tc.want, tc.name) + price := resolveFloorMin(&tc.reqFloors, &tc.fetchFloors, getCurrencyRates(rates)) + if !reflect.DeepEqual(price.FloorMin, tc.expPrice.FloorMin) { + t.Errorf("Floor Value error: \nreturn:\t%v\nwant:\t%v", price.FloorMin, tc.expPrice.FloorMin) + } + if !reflect.DeepEqual(price.FloorMinCur, tc.expPrice.FloorMinCur) { + t.Errorf("Floor Currency error: \nreturn:\t%v\nwant:\t%v", price.FloorMinCur, tc.expPrice.FloorMinCur) + } + }) } } -func TestIsPriceFloorsEnabled(t *testing.T) { +func TestMergeFloors(t *testing.T) { + + rates := map[string]map[string]float64{ + "USD": { + "INR": 70, + "EUR": 0.9, + "JPY": 5.09, + }, + } + type args struct { - account config.Account - bidRequestWrapper *openrtb_ext.RequestWrapper + reqFloors *openrtb_ext.PriceFloorRules + fetchFloors *openrtb_ext.PriceFloorRules } tests := []struct { name string args args - want bool + want *openrtb_ext.PriceFloorRules }{ { - name: "Disabled in account and req", + name: "Fetched Floors are present and request Floors are empty", args: args{ - account: config.Account{PriceFloors: config.AccountPriceFloors{Enabled: false}}, - bidRequestWrapper: &openrtb_ext.RequestWrapper{ - BidRequest: &openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"floors":{"enabled": false} }}`), + reqFloors: nil, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, }, }, }, - want: false, }, { - name: "Enabled in account and req", + name: "Fetched Floors are present and request Floors has floors disabled", args: args{ - account: config.Account{PriceFloors: config.AccountPriceFloors{Enabled: true}}, - bidRequestWrapper: &openrtb_ext.RequestWrapper{ - BidRequest: &openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"floors":{"enabled": true} }}`), + reqFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getFalse(), + }, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getFalse(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, }, }, }, - want: true, }, { - name: "disabled in account and enabled req", + name: "Fetched Floors are present and request Floors has enforcement (enforcepbs = true)", args: args{ - account: config.Account{PriceFloors: config.AccountPriceFloors{Enabled: false}}, - bidRequestWrapper: &openrtb_ext.RequestWrapper{ - BidRequest: &openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"floors":{"enabled": true} }}`), + reqFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getTrue(), + }, + }, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, }, }, }, - want: false, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getTrue(), + }, + }, }, { - name: "Enabled in account and disabled in req", + name: "Fetched Floors are present and request Floors has enforcement (enforcepbs = false)", args: args{ - account: config.Account{PriceFloors: config.AccountPriceFloors{Enabled: true}}, - bidRequestWrapper: &openrtb_ext.RequestWrapper{ - BidRequest: &openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"floors":{"enabled": false} }}`)}, + reqFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + }, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + }, + }, + { + name: "Fetched Floors are present and request Floors has Floormin", + args: args{ + reqFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + FloorMin: 5, + FloorMinCur: "INR", + }, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + FloorMin: 5, + FloorMinCur: "INR", + }, + }, + { + name: "Fetched Floors are present and request Floors has URL", + args: args{ + reqFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + FloorMin: 5, + FloorMinCur: "INR", + Location: &openrtb_ext.PriceFloorEndpoint{ + URL: "https://test.com/floors", + }, + }, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + FloorMin: 5, + FloorMinCur: "INR", + Location: &openrtb_ext.PriceFloorEndpoint{ + URL: "https://test.com/floors", + }, + }, + }, + { + name: "Fetched Floors has no enable atrribute are present and request Floors has URL", + args: args{ + reqFloors: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + FloorMin: 5, + FloorMinCur: "INR", + Location: &openrtb_ext.PriceFloorEndpoint{ + URL: "https://test.com/floors", + }, + }, + fetchFloors: &openrtb_ext.PriceFloorRules{ + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + want: &openrtb_ext.PriceFloorRules{ + Enabled: getTrue(), + Data: &openrtb_ext.PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{ + { + ModelVersion: "Version 1", + Currency: "INR", + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: openrtb_ext.PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + Enforcement: &openrtb_ext.PriceFloorEnforcement{ + EnforceRate: 50, + EnforcePBS: getFalse(), + }, + FloorMin: 5, + FloorMinCur: "INR", + Location: &openrtb_ext.PriceFloorEndpoint{ + URL: "https://test.com/floors", }, }, - want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := isPriceFloorsEnabled(tt.args.account, tt.args.bidRequestWrapper) - assert.Equal(t, got, tt.want, tt.name) + if got := mergeFloors(tt.args.reqFloors, tt.args.fetchFloors, getCurrencyRates(rates)); !reflect.DeepEqual(got, tt.want) { + t.Errorf("mergeFloors() = %v, want %v", got, tt.want) + } }) } } diff --git a/floors/rule.go b/floors/rule.go index f5f74cb6acf..65ddf31e727 100644 --- a/floors/rule.go +++ b/floors/rule.go @@ -8,9 +8,10 @@ import ( "strings" "github.com/golang/glog" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/ptrutil" ) const ( @@ -38,8 +39,7 @@ const ( // getFloorCurrency returns floors currency provided in floors JSON, // if currency is not provided then defaults to USD func getFloorCurrency(floorExt *openrtb_ext.PriceFloorRules) string { - var floorCur string - + floorCur := defaultCurrency if floorExt != nil && floorExt.Data != nil { if floorExt.Data.Currency != "" { floorCur = floorExt.Data.Currency @@ -50,10 +50,6 @@ func getFloorCurrency(floorExt *openrtb_ext.PriceFloorRules) string { } } - if len(floorCur) == 0 { - floorCur = defaultCurrency - } - return floorCur } @@ -296,8 +292,8 @@ func getSizeValue(imp *openrtb2.Imp) string { if imp.Banner != nil { width, height = getBannerSize(imp) } else if imp.Video != nil { - width = imp.Video.W - height = imp.Video.H + width = ptrutil.ValueOrDefault(imp.Video.W) + height = ptrutil.ValueOrDefault(imp.Video.H) } if width != 0 && height != 0 { diff --git a/floors/rule_test.go b/floors/rule_test.go index 7c484a7c95f..d356a768233 100644 --- a/floors/rule_test.go +++ b/floors/rule_test.go @@ -5,9 +5,10 @@ import ( "errors" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -195,7 +196,7 @@ func TestUpdateImpExtWithFloorDetails(t *testing.T) { matchedRule: "test|123|xyz", floorRuleVal: 5.5, floorVal: 5.5, - imp: &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{ID: "1234", Video: &openrtb2.Video{W: 300, H: 250}}}, + imp: &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{ID: "1234", Video: &openrtb2.Video{W: ptrutil.ToPtr[int64](300), H: ptrutil.ToPtr[int64](250)}}}, expected: []byte(`{"prebid":{"floors":{"floorrule":"test|123|xyz","floorrulevalue":5.5,"floorvalue":5.5}}}`), }, { @@ -203,7 +204,7 @@ func TestUpdateImpExtWithFloorDetails(t *testing.T) { matchedRule: "test|123|xyz", floorRuleVal: 5.5, floorVal: 5.5, - imp: &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{ID: "1234", Video: &openrtb2.Video{W: 300, H: 250}, Ext: json.RawMessage{}}}, + imp: &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{ID: "1234", Video: &openrtb2.Video{W: ptrutil.ToPtr[int64](300), H: ptrutil.ToPtr[int64](250)}, Ext: json.RawMessage{}}}, expected: []byte(`{"prebid":{"floors":{"floorrule":"test|123|xyz","floorrulevalue":5.5,"floorvalue":5.5}}}`), }, { @@ -211,7 +212,7 @@ func TestUpdateImpExtWithFloorDetails(t *testing.T) { matchedRule: "banner|www.test.com|*", floorRuleVal: 5.5, floorVal: 15.5, - imp: &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{ID: "1234", Video: &openrtb2.Video{W: 300, H: 250}, Ext: []byte(`{"prebid": {"test": true}}`)}}, + imp: &openrtb_ext.ImpWrapper{Imp: &openrtb2.Imp{ID: "1234", Video: &openrtb2.Video{W: ptrutil.ToPtr[int64](300), H: ptrutil.ToPtr[int64](250)}, Ext: []byte(`{"prebid": {"test": true}}`)}}, expected: []byte(`{"prebid":{"floors":{"floorrule":"banner|www.test.com|*","floorrulevalue":5.5,"floorvalue":15.5}}}`), }, } @@ -251,7 +252,7 @@ func TestCreateRuleKeys(t *testing.T) { Site: &openrtb2.Site{ Domain: "www.test.com", }, - Imp: []openrtb2.Imp{{ID: "1234", Video: &openrtb2.Video{W: 640, H: 480, Placement: 1}}}, + Imp: []openrtb2.Imp{{ID: "1234", Video: &openrtb2.Video{W: ptrutil.ToPtr[int64](640), H: ptrutil.ToPtr[int64](480), Placement: 1}}}, }, floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"mediaType", "size", "domain"}}, out: []string{"video", "640x480", "www.test.com"}, @@ -262,7 +263,7 @@ func TestCreateRuleKeys(t *testing.T) { Site: &openrtb2.Site{ Domain: "www.test.com", }, - Imp: []openrtb2.Imp{{ID: "1234", Video: &openrtb2.Video{W: 300, H: 250, Placement: 2}}}, + Imp: []openrtb2.Imp{{ID: "1234", Video: &openrtb2.Video{W: ptrutil.ToPtr[int64](300), H: ptrutil.ToPtr[int64](250), Placement: 2}}}, }, floorSchema: openrtb_ext.PriceFloorSchema{Delimiter: "|", Fields: []string{"mediaType", "size", "domain"}}, out: []string{"video-outstream", "300x250", "www.test.com"}, @@ -1015,7 +1016,7 @@ func TestGetSizeValue(t *testing.T) { }, { name: "video: imp.video.w and imp.video.h present", - imp: &openrtb2.Imp{Video: &openrtb2.Video{W: 120, H: 240}}, + imp: &openrtb2.Imp{Video: &openrtb2.Video{W: ptrutil.ToPtr[int64](120), H: ptrutil.ToPtr[int64](240)}}, want: "120x240", }, { @@ -1045,7 +1046,7 @@ func TestGetMediaType(t *testing.T) { }{ { name: "more than one of these: imp.banner, imp.video, imp.native, imp.audio present", - imp: &openrtb2.Imp{Video: &openrtb2.Video{W: 120, H: 240}, Banner: &openrtb2.Banner{W: getInt64Ptr(320), H: getInt64Ptr(240)}}, + imp: &openrtb2.Imp{Video: &openrtb2.Video{W: ptrutil.ToPtr[int64](120), H: ptrutil.ToPtr[int64](240)}, Banner: &openrtb2.Banner{W: getInt64Ptr(320), H: getInt64Ptr(240)}}, want: "*", }, { @@ -1055,12 +1056,12 @@ func TestGetMediaType(t *testing.T) { }, { name: "video-outstream present", - imp: &openrtb2.Imp{Video: &openrtb2.Video{W: 120, H: 240, Placement: 2}}, + imp: &openrtb2.Imp{Video: &openrtb2.Video{W: ptrutil.ToPtr[int64](120), H: ptrutil.ToPtr[int64](240), Placement: 2}}, want: "video-outstream", }, { name: "video-instream present", - imp: &openrtb2.Imp{Video: &openrtb2.Video{W: 120, H: 240, Placement: 1}}, + imp: &openrtb2.Imp{Video: &openrtb2.Video{W: ptrutil.ToPtr[int64](120), H: ptrutil.ToPtr[int64](240), Placement: 1}}, want: "video", }, { diff --git a/floors/validate.go b/floors/validate.go index 5624735c852..140df7def39 100644 --- a/floors/validate.go +++ b/floors/validate.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) var validSchemaDimensions = map[string]struct{}{ @@ -55,7 +55,7 @@ func validateFloorRulesAndLowerValidRuleKey(schema openrtb_ext.PriceFloorSchema, // validateFloorParams validates SchemaVersion, SkipRate and FloorMin func validateFloorParams(extFloorRules *openrtb_ext.PriceFloorRules) error { - if extFloorRules.Data != nil && len(extFloorRules.Data.FloorsSchemaVersion) > 0 && extFloorRules.Data.FloorsSchemaVersion != "2" { + if extFloorRules.Data != nil && extFloorRules.Data.FloorsSchemaVersion != 0 && extFloorRules.Data.FloorsSchemaVersion != 2 { return fmt.Errorf("Invalid FloorsSchemaVersion = '%v', supported version 2", extFloorRules.Data.FloorsSchemaVersion) } diff --git a/floors/validate_test.go b/floors/validate_test.go index 96dad819e06..36c1c73e0b5 100644 --- a/floors/validate_test.go +++ b/floors/validate_test.go @@ -5,8 +5,8 @@ import ( "fmt" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -56,13 +56,12 @@ func TestValidateFloorParams(t *testing.T) { Err: errors.New("Invalid FloorMin = '-10', value should be >= 0"), }, { - name: "Invalid FloorSchemaVersion ", + name: "Invalid FloorSchemaVersion 2", floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{ - FloorsSchemaVersion: "1", + FloorsSchemaVersion: 1, ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ ModelVersion: "Version 1", - - Schema: openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}, Delimiter: "|"}, + Schema: openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}, Delimiter: "|"}, Values: map[string]float64{ "banner|300x250|www.website.com": 1.01, "banner|300x600|*": 4.01, @@ -70,6 +69,46 @@ func TestValidateFloorParams(t *testing.T) { }}}, Err: errors.New("Invalid FloorsSchemaVersion = '1', supported version 2"), }, + { + name: "Invalid FloorSchemaVersion -2", + floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{ + FloorsSchemaVersion: -2, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + ModelVersion: "Version 1", + Schema: openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}, Delimiter: "|"}, + Values: map[string]float64{ + "banner|300x250|www.website.com": 1.01, + "banner|300x600|*": 4.01, + }, Default: 0.01}, + }}}, + Err: errors.New("Invalid FloorsSchemaVersion = '-2', supported version 2"), + }, + { + name: "Valid FloorSchemaVersion 0", + floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{ + FloorsSchemaVersion: 0, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + ModelVersion: "Version 1", + Schema: openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}, Delimiter: "|"}, + Values: map[string]float64{ + "banner|300x250|www.website.com": 1.01, + "banner|300x600|*": 4.01, + }, Default: 0.01}, + }}}, + }, + { + name: "Valid FloorSchemaVersion 2", + floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{ + FloorsSchemaVersion: 2, + ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ + ModelVersion: "Version 1", + Schema: openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}, Delimiter: "|"}, + Values: map[string]float64{ + "banner|300x250|www.website.com": 1.01, + "banner|300x600|*": 4.01, + }, Default: 0.01}, + }}}, + }, } for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { diff --git a/gdpr/aggregated_config.go b/gdpr/aggregated_config.go index bbfb503225d..3df5fdc7ff8 100644 --- a/gdpr/aggregated_config.go +++ b/gdpr/aggregated_config.go @@ -3,8 +3,8 @@ package gdpr import ( "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // TCF2ConfigReader is an interface to access TCF2 configurations @@ -17,7 +17,7 @@ type TCF2ConfigReader interface { PurposeEnforced(consentconstants.Purpose) bool PurposeEnforcementAlgo(consentconstants.Purpose) config.TCF2EnforcementAlgo PurposeEnforcingVendors(consentconstants.Purpose) bool - PurposeVendorExceptions(consentconstants.Purpose) map[openrtb_ext.BidderName]struct{} + PurposeVendorExceptions(consentconstants.Purpose) map[string]struct{} PurposeOneTreatmentEnabled() bool PurposeOneTreatmentAccessAllowed() bool } @@ -85,9 +85,9 @@ func (tc *tcf2Config) PurposeEnforcingVendors(purpose consentconstants.Purpose) } // PurposeVendorExceptions returns the vendor exception map for the specified purpose if it exists for the account; -// otherwise it returns a nil map. If a bidder is a vendor exception, the GDPR full enforcement algorithm will +// otherwise it returns a nil map. If a bidder/analytics adapter is a vendor exception, the GDPR full enforcement algorithm will // bypass the legal basis calculation assuming the request is valid and there isn't a "deny all" publisher restriction -func (tc *tcf2Config) PurposeVendorExceptions(purpose consentconstants.Purpose) map[openrtb_ext.BidderName]struct{} { +func (tc *tcf2Config) PurposeVendorExceptions(purpose consentconstants.Purpose) map[string]struct{} { if value, exists := tc.AccountConfig.PurposeVendorExceptions(purpose); exists { return value } diff --git a/gdpr/aggregated_config_test.go b/gdpr/aggregated_config_test.go index bf2d3bbb8f8..f577772cb6e 100644 --- a/gdpr/aggregated_config_test.go +++ b/gdpr/aggregated_config_test.go @@ -5,8 +5,8 @@ import ( "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -317,54 +317,54 @@ func TestPurposeEnforcingVendors(t *testing.T) { func TestPurposeVendorExceptions(t *testing.T) { tests := []struct { description string - givePurpose1HostExceptionMap map[openrtb_ext.BidderName]struct{} - givePurpose1AccountExceptionMap map[openrtb_ext.BidderName]struct{} - givePurpose2HostExceptionMap map[openrtb_ext.BidderName]struct{} - givePurpose2AccountExceptionMap map[openrtb_ext.BidderName]struct{} + givePurpose1HostExceptionMap map[string]struct{} + givePurpose1AccountExceptionMap map[string]struct{} + givePurpose2HostExceptionMap map[string]struct{} + givePurpose2AccountExceptionMap map[string]struct{} givePurpose consentconstants.Purpose - wantExceptionMap map[openrtb_ext.BidderName]struct{} + wantExceptionMap map[string]struct{} }{ { description: "Purpose 1 exception list set at account level - use empty account list", - givePurpose1HostExceptionMap: map[openrtb_ext.BidderName]struct{}{}, - givePurpose1AccountExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + givePurpose1HostExceptionMap: map[string]struct{}{}, + givePurpose1AccountExceptionMap: map[string]struct{}{}, givePurpose: 1, - wantExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + wantExceptionMap: map[string]struct{}{}, }, { description: "Purpose 1 exception list set at account level - use nonempty account list", - givePurpose1HostExceptionMap: map[openrtb_ext.BidderName]struct{}{}, - givePurpose1AccountExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}}, + givePurpose1HostExceptionMap: map[string]struct{}{}, + givePurpose1AccountExceptionMap: map[string]struct{}{"appnexus": {}, "rubicon": {}}, givePurpose: 1, - wantExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}}, + wantExceptionMap: map[string]struct{}{"appnexus": {}, "rubicon": {}}, }, { description: "Purpose 1 exception list not set at account level - use empty host list", - givePurpose1HostExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + givePurpose1HostExceptionMap: map[string]struct{}{}, givePurpose1AccountExceptionMap: nil, givePurpose: 1, - wantExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + wantExceptionMap: map[string]struct{}{}, }, { description: "Purpose 1 exception list not set at account level - use nonempty host list", - givePurpose1HostExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}}, + givePurpose1HostExceptionMap: map[string]struct{}{"appnexus": {}, "rubicon": {}}, givePurpose1AccountExceptionMap: nil, givePurpose: 1, - wantExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}}, + wantExceptionMap: map[string]struct{}{"appnexus": {}, "rubicon": {}}, }, { description: "Purpose 1 exception list not set at account level or host level", givePurpose1HostExceptionMap: nil, givePurpose1AccountExceptionMap: nil, givePurpose: 1, - wantExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + wantExceptionMap: map[string]struct{}{}, }, { description: "Some other purpose exception list set at account level", - givePurpose2HostExceptionMap: map[openrtb_ext.BidderName]struct{}{}, - givePurpose2AccountExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}}, + givePurpose2HostExceptionMap: map[string]struct{}{}, + givePurpose2AccountExceptionMap: map[string]struct{}{"appnexus": {}, "rubicon": {}}, givePurpose: 2, - wantExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}}, + wantExceptionMap: map[string]struct{}{"appnexus": {}, "rubicon": {}}, }, } diff --git a/gdpr/basic_enforcement.go b/gdpr/basic_enforcement.go index f4559c4643d..2bb11857a16 100644 --- a/gdpr/basic_enforcement.go +++ b/gdpr/basic_enforcement.go @@ -2,10 +2,9 @@ package gdpr import ( tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" - "github.com/prebid/prebid-server/openrtb_ext" ) -// BasicEnforcement determines if legal basis is satisfied for a given purpose and bidder using +// BasicEnforcement determines if legal basis is satisfied for a given purpose and bidder/analytics adapter using // the TCF2 basic enforcement algorithm. The algorithm is a high-level mode of consent confirmation // that looks for a good-faith indication that the user has provided consent or legal basis signals // necessary to perform a privacy-protected activity. The algorithm does not involve the GVL. @@ -14,21 +13,21 @@ type BasicEnforcement struct { cfg purposeConfig } -// LegalBasis determines if legal basis is satisfied for a given purpose and bidder based on user consent +// LegalBasis determines if legal basis is satisfied for a given purpose and bidder/analytics adapter based on user consent // and legal basis signals. -func (be *BasicEnforcement) LegalBasis(vendorInfo VendorInfo, bidder openrtb_ext.BidderName, consent tcf2.ConsentMetadata, overrides Overrides) bool { +func (be *BasicEnforcement) LegalBasis(vendorInfo VendorInfo, name string, consent tcf2.ConsentMetadata, overrides Overrides) bool { enforcePurpose, enforceVendors := be.applyEnforceOverrides(overrides) if !enforcePurpose && !enforceVendors { return true } - if be.cfg.vendorException(bidder) && !overrides.blockVendorExceptions { + if be.cfg.vendorException(name) && !overrides.blockVendorExceptions { return true } - if !enforcePurpose && be.cfg.basicEnforcementVendor(bidder) { + if !enforcePurpose && be.cfg.basicEnforcementVendor(name) { return true } - if enforcePurpose && consent.PurposeAllowed(be.cfg.PurposeID) && be.cfg.basicEnforcementVendor(bidder) { + if enforcePurpose && consent.PurposeAllowed(be.cfg.PurposeID) && be.cfg.basicEnforcementVendor(name) { return true } if enforcePurpose && consent.PurposeLITransparency(be.cfg.PurposeID) && overrides.allowLITransparency { diff --git a/gdpr/basic_enforcement_test.go b/gdpr/basic_enforcement_test.go index c49e59ea595..7bb0076940b 100644 --- a/gdpr/basic_enforcement_test.go +++ b/gdpr/basic_enforcement_test.go @@ -6,13 +6,16 @@ import ( "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/go-gdpr/vendorconsent" tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) func TestBasicLegalBasis(t *testing.T) { - appnexusID := uint16(32) + var ( + appnexus = string(openrtb_ext.BidderAppnexus) + appnexusID = uint16(32) + ) noConsents := "CPerMsAPerMsAAAAAAENCfCAAAAAAAAAAAAAAAAAAAAA" purpose2Consent := "CPerMsAPerMsAAAAAAENCfCAAEAAAAAAAAAAAAAAAAAA" @@ -149,7 +152,7 @@ func TestBasicLegalBasis(t *testing.T) { PurposeID: consentconstants.Purpose(2), EnforcePurpose: false, EnforceVendors: true, - BasicEnforcementVendorsMap: map[string]struct{}{string(openrtb_ext.BidderAppnexus): {}}, + BasicEnforcementVendorsMap: map[string]struct{}{appnexus: {}}, }, wantResult: true, }, @@ -170,7 +173,7 @@ func TestBasicLegalBasis(t *testing.T) { PurposeID: consentconstants.Purpose(2), EnforcePurpose: true, EnforceVendors: true, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + VendorExceptionMap: map[string]struct{}{appnexus: {}}, }, wantResult: true, }, @@ -181,7 +184,7 @@ func TestBasicLegalBasis(t *testing.T) { PurposeID: consentconstants.Purpose(2), EnforcePurpose: true, EnforceVendors: true, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + VendorExceptionMap: map[string]struct{}{appnexus: {}}, }, overrides: Overrides{blockVendorExceptions: true}, wantResult: false, @@ -203,7 +206,7 @@ func TestBasicLegalBasis(t *testing.T) { PurposeID: consentconstants.Purpose(2), EnforcePurpose: true, EnforceVendors: true, - BasicEnforcementVendorsMap: map[string]struct{}{string(openrtb_ext.BidderAppnexus): {}}, + BasicEnforcementVendorsMap: map[string]struct{}{appnexus: {}}, }, wantResult: true, }, @@ -233,7 +236,7 @@ func TestBasicLegalBasis(t *testing.T) { enforcer := BasicEnforcement{cfg: tt.config} vendorInfo := VendorInfo{vendorID: appnexusID, vendor: nil} - result := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + result := enforcer.LegalBasis(vendorInfo, appnexus, consentMeta, tt.overrides) assert.Equal(t, tt.wantResult, result, tt.description) } diff --git a/gdpr/full_enforcement.go b/gdpr/full_enforcement.go index eefa28d5499..9c2b5221385 100644 --- a/gdpr/full_enforcement.go +++ b/gdpr/full_enforcement.go @@ -2,7 +2,6 @@ package gdpr import ( tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" - "github.com/prebid/prebid-server/openrtb_ext" ) const ( @@ -11,7 +10,7 @@ const ( pubRestrictRequireLegitInterest = 2 ) -// FullEnforcement determines if legal basis is satisfied for a given purpose and bidder using +// FullEnforcement determines if legal basis is satisfied for a given purpose and bidde/analytics adapterr using // the TCF2 full enforcement algorithm. The algorithm is a detailed confirmation that reads the // GVL, interprets the consent string and performs legal basis analysis necessary to perform a // privacy-protected activity. @@ -20,9 +19,9 @@ type FullEnforcement struct { cfg purposeConfig } -// LegalBasis determines if legal basis is satisfied for a given purpose and bidder based on the +// LegalBasis determines if legal basis is satisfied for a given purpose and bidder/analytics adapter based on the // vendor claims in the GVL, publisher restrictions and user consent. -func (fe *FullEnforcement) LegalBasis(vendorInfo VendorInfo, bidder openrtb_ext.BidderName, consent tcf2.ConsentMetadata, overrides Overrides) bool { +func (fe *FullEnforcement) LegalBasis(vendorInfo VendorInfo, name string, consent tcf2.ConsentMetadata, overrides Overrides) bool { enforcePurpose, enforceVendors := fe.applyEnforceOverrides(overrides) if consent.CheckPubRestriction(uint8(fe.cfg.PurposeID), pubRestrictNotAllowed, vendorInfo.vendorID) { @@ -31,7 +30,7 @@ func (fe *FullEnforcement) LegalBasis(vendorInfo VendorInfo, bidder openrtb_ext. if !enforcePurpose && !enforceVendors { return true } - if fe.cfg.vendorException(bidder) && !overrides.blockVendorExceptions { + if fe.cfg.vendorException(name) && !overrides.blockVendorExceptions { return true } diff --git a/gdpr/full_enforcement_test.go b/gdpr/full_enforcement_test.go index 4a859ecaabb..eed86bf2383 100644 --- a/gdpr/full_enforcement_test.go +++ b/gdpr/full_enforcement_test.go @@ -8,14 +8,17 @@ import ( tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/go-gdpr/vendorlist2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" "github.com/stretchr/testify/assert" ) func TestLegalBasisWithPubRestrictionAllowNone(t *testing.T) { - appnexusID := uint16(32) + var ( + appnexus = string(openrtb_ext.BidderAppnexus) + appnexusID = uint16(32) + ) NoConsentsWithP1P2P3V32RestrictionAllowNone := "CPfMKEAPfMKEAAAAAAENCgCAAAAAAAAAAAAAAQAAAAAAAIAAAAAAAGCAAgAgCAAQAQBgAIAIAAAA" P1P2P3PurposeConsentAndV32VendorConsentWithP1P2P3V32RestrictionAllowNone := "CPfMKEAPfMKEAAAAAAENCgCAAOAAAAAAAAAAAQAAAAAEAIAAAAAAAGCAAgAgCAAQAQBgAIAIAAAA" @@ -44,7 +47,7 @@ func TestLegalBasisWithPubRestrictionAllowNone(t *testing.T) { config: purposeConfig{ EnforcePurpose: true, EnforceVendors: true, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + VendorExceptionMap: map[string]struct{}{appnexus: {}}, }, consent: NoConsentsWithP1P2P3V32RestrictionAllowNone, wantConsentPurposeResult: false, @@ -80,21 +83,24 @@ func TestLegalBasisWithPubRestrictionAllowNone(t *testing.T) { enforcer := FullEnforcement{cfg: tt.config} enforcer.cfg.PurposeID = consentconstants.Purpose(1) - consentPurposeResult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, Overrides{}) + consentPurposeResult := enforcer.LegalBasis(vendorInfo, appnexus, consentMeta, Overrides{}) assert.Equal(t, tt.wantConsentPurposeResult, consentPurposeResult, tt.description+" -- GVL consent purpose") enforcer.cfg.PurposeID = consentconstants.Purpose(2) - LIPurposeresult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, Overrides{}) + LIPurposeresult := enforcer.LegalBasis(vendorInfo, appnexus, consentMeta, Overrides{}) assert.Equal(t, tt.wantLIPurposeResult, LIPurposeresult, tt.description+" -- GVL LI purpose") enforcer.cfg.PurposeID = consentconstants.Purpose(3) - flexPurposeResult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, Overrides{}) + flexPurposeResult := enforcer.LegalBasis(vendorInfo, appnexus, consentMeta, Overrides{}) assert.Equal(t, tt.wantFlexPurposeResult, flexPurposeResult, tt.description+" -- GVL flex purpose") } } func TestLegalBasisWithNoPubRestrictionsAndWithPubRestrictionAllowAll(t *testing.T) { - appnexusID := uint16(32) + var ( + appnexus = string(openrtb_ext.BidderAppnexus) + appnexusID = uint16(32) + ) NoConsents := "CPfCRQAPfCRQAAAAAAENCgCAAAAAAAAAAAAAAAAAAAAA" P1P2P3PurposeConsent := "CPfCRQAPfCRQAAAAAAENCgCAAOAAAAAAAAAAAAAAAAAA" @@ -325,7 +331,7 @@ func TestLegalBasisWithNoPubRestrictionsAndWithPubRestrictionAllowAll(t *testing config: purposeConfig{ EnforcePurpose: true, EnforceVendors: true, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + VendorExceptionMap: map[string]struct{}{appnexus: {}}, }, consentNoPubRestriction: NoConsents, consentWithPubRestriction: NoConsentsWithP1P2P3V32RestrictionAllowAll, @@ -338,7 +344,7 @@ func TestLegalBasisWithNoPubRestrictionsAndWithPubRestrictionAllowAll(t *testing config: purposeConfig{ EnforcePurpose: true, EnforceVendors: true, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + VendorExceptionMap: map[string]struct{}{appnexus: {}}, }, consentNoPubRestriction: NoConsents, consentWithPubRestriction: NoConsentsWithP1P2P3V32RestrictionAllowAll, @@ -370,22 +376,25 @@ func TestLegalBasisWithNoPubRestrictionsAndWithPubRestrictionAllowAll(t *testing enforcer := FullEnforcement{cfg: tt.config} enforcer.cfg.PurposeID = consentconstants.Purpose(1) - consentPurposeResult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + consentPurposeResult := enforcer.LegalBasis(vendorInfo, appnexus, consentMeta, tt.overrides) assert.Equal(t, tt.wantConsentPurposeResult, consentPurposeResult, tt.description+" -- GVL consent purpose -- consent string %d of %d", i+1, len(consents)) enforcer.cfg.PurposeID = consentconstants.Purpose(2) - LIPurposeresult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + LIPurposeresult := enforcer.LegalBasis(vendorInfo, appnexus, consentMeta, tt.overrides) assert.Equal(t, tt.wantLIPurposeResult, LIPurposeresult, tt.description+" -- GVL LI purpose -- consent string %d of %d", i+1, len(consents)) enforcer.cfg.PurposeID = consentconstants.Purpose(3) - flexPurposeResult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + flexPurposeResult := enforcer.LegalBasis(vendorInfo, appnexus, consentMeta, tt.overrides) assert.Equal(t, tt.wantFlexPurposeResult, flexPurposeResult, tt.description+" -- GVL flex purpose -- consent string %d of %d", i+1, len(consents)) } } } func TestLegalBasisWithPubRestrictionRequireConsent(t *testing.T) { - appnexusID := uint16(32) + var ( + appnexus = string(openrtb_ext.BidderAppnexus) + appnexusID = uint16(32) + ) NoConsentsWithP1P2P3V32RestrictionRequireConsent := "CPfFkMAPfFkMAAAAAAENCgCAAAAAAAAAAAAAAQAAAAAAAIAAAAAAAGCgAgAgCQAQAQBoAIAIAAAA" P1P2P3PurposeConsentWithP1P2P3V32RestrictionRequireConsent := "CPfFkMAPfFkMAAAAAAENCgCAAOAAAAAAAAAAAQAAAAAAAIAAAAAAAGCgAgAgCQAQAQBoAIAIAAAA" @@ -590,7 +599,7 @@ func TestLegalBasisWithPubRestrictionRequireConsent(t *testing.T) { config: purposeConfig{ EnforcePurpose: true, EnforceVendors: true, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + VendorExceptionMap: map[string]struct{}{appnexus: {}}, }, consent: NoConsentsWithP1P2P3V32RestrictionRequireConsent, wantConsentPurposeResult: true, @@ -602,7 +611,7 @@ func TestLegalBasisWithPubRestrictionRequireConsent(t *testing.T) { config: purposeConfig{ EnforcePurpose: true, EnforceVendors: true, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + VendorExceptionMap: map[string]struct{}{appnexus: {}}, }, consent: NoConsentsWithP1P2P3V32RestrictionRequireConsent, overrides: Overrides{blockVendorExceptions: true}, @@ -628,21 +637,24 @@ func TestLegalBasisWithPubRestrictionRequireConsent(t *testing.T) { enforcer := FullEnforcement{cfg: tt.config} enforcer.cfg.PurposeID = consentconstants.Purpose(1) - consentPurposeResult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + consentPurposeResult := enforcer.LegalBasis(vendorInfo, appnexus, consentMeta, tt.overrides) assert.Equal(t, tt.wantConsentPurposeResult, consentPurposeResult, tt.description+" -- GVL consent purpose") enforcer.cfg.PurposeID = consentconstants.Purpose(2) - LIPurposeresult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + LIPurposeresult := enforcer.LegalBasis(vendorInfo, appnexus, consentMeta, tt.overrides) assert.Equal(t, tt.wantLIPurposeResult, LIPurposeresult, tt.description+" -- GVL LI purpose") enforcer.cfg.PurposeID = consentconstants.Purpose(3) - flexPurposeResult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + flexPurposeResult := enforcer.LegalBasis(vendorInfo, appnexus, consentMeta, tt.overrides) assert.Equal(t, tt.wantFlexPurposeResult, flexPurposeResult, tt.description+" -- GVL flex purpose") } } func TestLegalBasisWithPubRestrictionRequireLI(t *testing.T) { - appnexusID := uint16(32) + var ( + appnexus = string(openrtb_ext.BidderAppnexus) + appnexusID = uint16(32) + ) NoConsentsWithP1P2P3V32RestrictionRequireLI := "CPfFkMAPfFkMAAAAAAENCgCAAAAAAAAAAAAAAQAAAAAAAIAAAAAAAGDAAgAgCgAQAQBwAIAIAAAA" P1P2P3PurposeConsentWithP1P2P3V32RestrictionRequireLI := "CPfFkMAPfFkMAAAAAAENCgCAAOAAAAAAAAAAAQAAAAAAAIAAAAAAAGDAAgAgCgAQAQBwAIAIAAAA" @@ -847,7 +859,7 @@ func TestLegalBasisWithPubRestrictionRequireLI(t *testing.T) { config: purposeConfig{ EnforcePurpose: true, EnforceVendors: true, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + VendorExceptionMap: map[string]struct{}{appnexus: {}}, }, consent: NoConsentsWithP1P2P3V32RestrictionRequireLI, wantConsentPurposeResult: true, @@ -859,7 +871,7 @@ func TestLegalBasisWithPubRestrictionRequireLI(t *testing.T) { config: purposeConfig{ EnforcePurpose: true, EnforceVendors: true, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + VendorExceptionMap: map[string]struct{}{appnexus: {}}, }, consent: NoConsentsWithP1P2P3V32RestrictionRequireLI, overrides: Overrides{blockVendorExceptions: true}, @@ -885,20 +897,21 @@ func TestLegalBasisWithPubRestrictionRequireLI(t *testing.T) { enforcer := FullEnforcement{cfg: tt.config} enforcer.cfg.PurposeID = consentconstants.Purpose(1) - consentPurposeResult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + consentPurposeResult := enforcer.LegalBasis(vendorInfo, appnexus, consentMeta, tt.overrides) assert.Equal(t, tt.wantConsentPurposeResult, consentPurposeResult, tt.description+" -- GVL consent purpose") enforcer.cfg.PurposeID = consentconstants.Purpose(2) - LIPurposeresult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + LIPurposeresult := enforcer.LegalBasis(vendorInfo, appnexus, consentMeta, tt.overrides) assert.Equal(t, tt.wantLIPurposeResult, LIPurposeresult, tt.description+" -- GVL LI purpose") enforcer.cfg.PurposeID = consentconstants.Purpose(3) - flexPurposeResult := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, tt.overrides) + flexPurposeResult := enforcer.LegalBasis(vendorInfo, appnexus, consentMeta, tt.overrides) assert.Equal(t, tt.wantFlexPurposeResult, flexPurposeResult, tt.description+" -- GVL flex purpose") } } func TestLegalBasisWithoutVendor(t *testing.T) { + appnexus := string(openrtb_ext.BidderAppnexus) P1P2P3PurposeConsent := "CPfCRQAPfCRQAAAAAAENCgCAAOAAAAAAAAAAAAAAAAAA" tests := []struct { name string @@ -918,7 +931,7 @@ func TestLegalBasisWithoutVendor(t *testing.T) { config: purposeConfig{ EnforcePurpose: true, EnforceVendors: false, - VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + VendorExceptionMap: map[string]struct{}{appnexus: {}}, }, wantResult: true, }, @@ -952,7 +965,7 @@ func TestLegalBasisWithoutVendor(t *testing.T) { enforcer := FullEnforcement{cfg: tt.config} enforcer.cfg.PurposeID = consentconstants.Purpose(3) - result := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, Overrides{}) + result := enforcer.LegalBasis(vendorInfo, appnexus, consentMeta, Overrides{}) assert.Equal(t, tt.wantResult, result) }) } diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index db6aa125383..20c5e9306be 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -3,8 +3,8 @@ package gdpr import ( "context" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) type Permissions interface { @@ -20,8 +20,8 @@ type Permissions interface { // Determines whether or not to send PI information to a bidder, or mask it out. // - // If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent. - AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions AuctionPermissions, err error) + // If the consent string was nonsensical, the no permissions are granted. + AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) AuctionPermissions } type PermissionsBuilder func(TCF2ConfigReader, RequestInfo) Permissions diff --git a/gdpr/gdpr_test.go b/gdpr/gdpr_test.go index 1e56c5fdede..db81d1396a8 100644 --- a/gdpr/gdpr_test.go +++ b/gdpr/gdpr_test.go @@ -6,8 +6,8 @@ import ( "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/go-gdpr/vendorlist" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -60,6 +60,6 @@ type fakePurposeEnforcerBuilder struct { purposeEnforcer PurposeEnforcer } -func (fpeb fakePurposeEnforcerBuilder) Builder(consentconstants.Purpose, openrtb_ext.BidderName) PurposeEnforcer { +func (fpeb fakePurposeEnforcerBuilder) Builder(consentconstants.Purpose, string) PurposeEnforcer { return fpeb.purposeEnforcer } diff --git a/gdpr/impl.go b/gdpr/impl.go index cc88a1fd3c6..47fd4521c1c 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -6,7 +6,7 @@ import ( "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/consentconstants" tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) const noBidder openrtb_ext.BidderName = "" @@ -48,7 +48,7 @@ func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ id, ok := p.vendorIDs[bidder] if ok { vendorExceptions := p.cfg.PurposeVendorExceptions(consentconstants.Purpose(1)) - _, vendorException := vendorExceptions[bidder] + _, vendorException := vendorExceptions[string(bidder)] return p.allowSync(ctx, id, bidder, vendorException) } @@ -56,33 +56,36 @@ func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ } // AuctionActivitiesAllowed determines whether auction activities are permitted for a given bidder -func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions AuctionPermissions, err error) { +func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) AuctionPermissions { if _, ok := p.nonStandardPublishers[p.publisherID]; ok { - return AllowAll, nil + return AllowAll } + if p.gdprSignal != SignalYes { - return AllowAll, nil + return AllowAll } + if p.consent == "" { - return p.defaultPermissions(), nil + return p.defaultPermissions() } + pc, err := parseConsent(p.consent) if err != nil { - return p.defaultPermissions(), err + return p.defaultPermissions() } + vendorID, _ := p.resolveVendorID(bidderCoreName, bidder) vendor, err := p.getVendor(ctx, vendorID, *pc) if err != nil { - return p.defaultPermissions(), err + return p.defaultPermissions() } - vendorInfo := VendorInfo{vendorID: vendorID, vendor: vendor} - - permissions = AuctionPermissions{} - permissions.AllowBidRequest = p.allowBidRequest(bidderCoreName, pc.consentMeta, vendorInfo) - permissions.PassGeo = p.allowGeo(bidderCoreName, pc.consentMeta, vendor) - permissions.PassID = p.allowID(bidderCoreName, pc.consentMeta, vendorInfo) - return permissions, nil + vendorInfo := VendorInfo{vendorID: vendorID, vendor: vendor} + return AuctionPermissions{ + AllowBidRequest: p.allowBidRequest(bidderCoreName, pc.consentMeta, vendorInfo), + PassGeo: p.allowGeo(bidderCoreName, pc.consentMeta, vendor), + PassID: p.allowID(bidderCoreName, pc.consentMeta, vendorInfo), + } } // defaultPermissions returns a permissions object that denies passing user IDs while @@ -138,9 +141,9 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, bidder } purpose := consentconstants.Purpose(1) - enforcer := p.purposeEnforcerBuilder(purpose, bidder) + enforcer := p.purposeEnforcerBuilder(purpose, string(bidder)) - if enforcer.LegalBasis(vendorInfo, bidder, pc.consentMeta, Overrides{blockVendorExceptions: !vendorException}) { + if enforcer.LegalBasis(vendorInfo, string(bidder), pc.consentMeta, Overrides{blockVendorExceptions: !vendorException}) { return true, nil } return false, nil @@ -149,13 +152,13 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, bidder // allowBidRequest computes legal basis for a given bidder using the enforcement algorithms selected // by the purpose enforcer builder func (p *permissionsImpl) allowBidRequest(bidder openrtb_ext.BidderName, consentMeta tcf2.ConsentMetadata, vendorInfo VendorInfo) bool { - enforcer := p.purposeEnforcerBuilder(consentconstants.Purpose(2), bidder) + enforcer := p.purposeEnforcerBuilder(consentconstants.Purpose(2), string(bidder)) overrides := Overrides{} if _, ok := enforcer.(*BasicEnforcement); ok { overrides.allowLITransparency = true } - return enforcer.LegalBasis(vendorInfo, bidder, consentMeta, overrides) + return enforcer.LegalBasis(vendorInfo, string(bidder), consentMeta, overrides) } // allowGeo computes legal basis for a given bidder using the configs, consent and GVL pertaining to @@ -180,13 +183,13 @@ func (p *permissionsImpl) allowGeo(bidder openrtb_ext.BidderName, consentMeta tc func (p *permissionsImpl) allowID(bidder openrtb_ext.BidderName, consentMeta tcf2.ConsentMetadata, vendorInfo VendorInfo) bool { for i := 2; i <= 10; i++ { purpose := consentconstants.Purpose(i) - enforcer := p.purposeEnforcerBuilder(purpose, bidder) + enforcer := p.purposeEnforcerBuilder(purpose, string(bidder)) overrides := Overrides{enforcePurpose: true, enforceVendors: true} if _, ok := enforcer.(*BasicEnforcement); ok && purpose == consentconstants.Purpose(2) { overrides.allowLITransparency = true } - if enforcer.LegalBasis(vendorInfo, bidder, consentMeta, overrides) { + if enforcer.LegalBasis(vendorInfo, string(bidder), consentMeta, overrides) { return true } } @@ -222,6 +225,6 @@ func (a AlwaysAllow) HostCookiesAllowed(ctx context.Context) (bool, error) { func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName) (bool, error) { return true, nil } -func (a AlwaysAllow) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions AuctionPermissions, err error) { - return AllowAll, nil +func (a AlwaysAllow) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) AuctionPermissions { + return AllowAll } diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index 835a580f6e2..79d43d58232 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -9,8 +9,8 @@ import ( "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/go-gdpr/vendorlist2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -335,9 +335,8 @@ func TestAllowActivities(t *testing.T) { perms.gdprSignal = tt.gdpr perms.publisherID = tt.publisherID - permissions, err := perms.AuctionActivitiesAllowed(context.Background(), tt.bidderCoreName, tt.bidderName) + permissions := perms.AuctionActivitiesAllowed(context.Background(), tt.bidderCoreName, tt.bidderName) - assert.Nil(t, err, tt.description) assert.Equal(t, tt.passID, permissions.PassID, tt.description) } } @@ -350,7 +349,7 @@ func TestAllowActivitiesBidderWithoutGVLID(t *testing.T) { tests := []struct { name string enforceAlgoID config.TCF2EnforcementAlgo - vendorExceptions map[openrtb_ext.BidderName]struct{} + vendorExceptions map[string]struct{} basicEnforcementVendors map[string]struct{} consent string allowBidRequest bool @@ -364,7 +363,7 @@ func TestAllowActivitiesBidderWithoutGVLID(t *testing.T) { { name: "full_enforcement_vendor_exception_user_consents_to_purpose_2", enforceAlgoID: config.TCF2FullEnforcement, - vendorExceptions: map[openrtb_ext.BidderName]struct{}{bidderWithoutGVLID: {}}, + vendorExceptions: map[string]struct{}{string(bidderWithoutGVLID): {}}, consent: purpose2Consent, allowBidRequest: true, passID: true, @@ -375,7 +374,7 @@ func TestAllowActivitiesBidderWithoutGVLID(t *testing.T) { }, { name: "basic_enforcement_vendor_exception_user_consents_to_purpose_2", - vendorExceptions: map[openrtb_ext.BidderName]struct{}{bidderWithoutGVLID: {}}, + vendorExceptions: map[string]struct{}{string(bidderWithoutGVLID): {}}, consent: purpose2Consent, allowBidRequest: true, passID: true, @@ -437,8 +436,7 @@ func TestAllowActivitiesBidderWithoutGVLID(t *testing.T) { purposeEnforcerBuilder: NewPurposeEnforcerBuilder(&tcf2AggConfig), } - permissions, err := perms.AuctionActivitiesAllowed(context.Background(), bidderWithoutGVLID, bidderWithoutGVLID) - assert.NoError(t, err) + permissions := perms.AuctionActivitiesAllowed(context.Background(), bidderWithoutGVLID, bidderWithoutGVLID) assert.Equal(t, tt.allowBidRequest, permissions.AllowBidRequest) assert.Equal(t, tt.passID, permissions.PassID) }) @@ -658,8 +656,7 @@ func TestAllowActivitiesGeoAndID(t *testing.T) { perms.consent = td.consent perms.purposeEnforcerBuilder = NewPurposeEnforcerBuilder(&tcf2AggConfig) - permissions, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder) - assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description) + permissions := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder) assert.EqualValuesf(t, td.allowBidRequest, permissions.AllowBidRequest, "AllowBid failure on %s", td.description) assert.EqualValuesf(t, td.passGeo, permissions.PassGeo, "PassGeo failure on %s", td.description) assert.EqualValuesf(t, td.passID, permissions.PassID, "PassID failure on %s", td.description) @@ -695,8 +692,7 @@ func TestAllowActivitiesWhitelist(t *testing.T) { } // Assert that an item that otherwise would not be allowed PI access, gets approved because it is found in the GDPR.NonStandardPublishers array - permissions, err := perms.AuctionActivitiesAllowed(context.Background(), openrtb_ext.BidderAppnexus, openrtb_ext.BidderAppnexus) - assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed") + permissions := perms.AuctionActivitiesAllowed(context.Background(), openrtb_ext.BidderAppnexus, openrtb_ext.BidderAppnexus) assert.EqualValuesf(t, true, permissions.PassGeo, "PassGeo failure") assert.EqualValuesf(t, true, permissions.PassID, "PassID failure") } @@ -767,8 +763,7 @@ func TestAllowActivitiesPubRestrict(t *testing.T) { perms.aliasGVLIDs = td.aliasGVLIDs perms.consent = td.consent - permissions, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder) - assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description) + permissions := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder) assert.EqualValuesf(t, td.passGeo, permissions.PassGeo, "PassGeo failure on %s", td.description) assert.EqualValuesf(t, td.passID, permissions.PassID, "PassID failure on %s", td.description) } @@ -1101,8 +1096,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { perms.cfg = &tcf2AggConfig perms.purposeEnforcerBuilder = NewPurposeEnforcerBuilder(&tcf2AggConfig) - permissions, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder) - assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description) + permissions := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder) assert.EqualValuesf(t, td.allowBidRequest, permissions.AllowBidRequest, "AllowBid failure on %s", td.description) assert.EqualValuesf(t, td.passGeo, permissions.PassGeo, "PassGeo failure on %s", td.description) assert.EqualValuesf(t, td.passID, permissions.PassID, "PassID failure on %s", td.description) @@ -1110,12 +1104,13 @@ func TestAllowActivitiesBidRequests(t *testing.T) { } func TestAllowActivitiesVendorException(t *testing.T) { + appnexus := string(openrtb_ext.BidderAppnexus) noPurposeOrVendorConsentAndPubRestrictsP2 := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAACEAAAACEAAgAgAA" noPurposeOrVendorConsentAndPubRestrictsNone := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAACEAAAAA" testDefs := []struct { description string - p2VendorExceptionMap map[openrtb_ext.BidderName]struct{} + p2VendorExceptionMap map[string]struct{} sf1VendorExceptionMap map[openrtb_ext.BidderName]struct{} bidder openrtb_ext.BidderName consent string @@ -1126,7 +1121,7 @@ func TestAllowActivitiesVendorException(t *testing.T) { }{ { description: "Bid/ID blocked by publisher - p2 enabled with p2 vendor exception, pub restricts p2 for vendor", - p2VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + p2VendorExceptionMap: map[string]struct{}{appnexus: {}}, bidder: openrtb_ext.BidderAppnexus, bidderCoreName: openrtb_ext.BidderAppnexus, consent: noPurposeOrVendorConsentAndPubRestrictsP2, @@ -1136,7 +1131,7 @@ func TestAllowActivitiesVendorException(t *testing.T) { }, { description: "Bid/ID allowed by vendor exception - p2 enabled with p2 vendor exception, pub restricts none", - p2VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + p2VendorExceptionMap: map[string]struct{}{appnexus: {}}, sf1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, bidder: openrtb_ext.BidderAppnexus, bidderCoreName: openrtb_ext.BidderAppnexus, @@ -1147,7 +1142,7 @@ func TestAllowActivitiesVendorException(t *testing.T) { }, { description: "Geo blocked - sf1 enabled but no consent", - p2VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + p2VendorExceptionMap: map[string]struct{}{}, sf1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, bidder: openrtb_ext.BidderAppnexus, bidderCoreName: openrtb_ext.BidderAppnexus, @@ -1158,7 +1153,7 @@ func TestAllowActivitiesVendorException(t *testing.T) { }, { description: "Geo allowed by vendor exception - sf1 enabled with sf1 vendor exception", - p2VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + p2VendorExceptionMap: map[string]struct{}{}, sf1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, bidder: openrtb_ext.BidderAppnexus, bidderCoreName: openrtb_ext.BidderAppnexus, @@ -1195,8 +1190,7 @@ func TestAllowActivitiesVendorException(t *testing.T) { perms.cfg = &tcf2AggConfig perms.purposeEnforcerBuilder = NewPurposeEnforcerBuilder(&tcf2AggConfig) - permissions, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder) - assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description) + permissions := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder) assert.EqualValuesf(t, td.allowBidRequest, permissions.AllowBidRequest, "AllowBid failure on %s", td.description) assert.EqualValuesf(t, td.passGeo, permissions.PassGeo, "PassGeo failure on %s", td.description) assert.EqualValuesf(t, td.passID, permissions.PassID, "PassID failure on %s", td.description) @@ -1204,33 +1198,34 @@ func TestAllowActivitiesVendorException(t *testing.T) { } func TestBidderSyncAllowedVendorException(t *testing.T) { + appnexus := string(openrtb_ext.BidderAppnexus) noPurposeOrVendorConsentAndPubRestrictsP1 := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAAQAAAAAAAAAAIIACACA" noPurposeOrVendorConsentAndPubRestrictsNone := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAACEAAAAA" testDefs := []struct { description string - p1VendorExceptionMap map[openrtb_ext.BidderName]struct{} + p1VendorExceptionMap map[string]struct{} bidder openrtb_ext.BidderName consent string allowSync bool }{ { description: "Sync blocked by no consent - p1 enabled, no p1 vendor exception, pub restricts none", - p1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + p1VendorExceptionMap: map[string]struct{}{}, bidder: openrtb_ext.BidderAppnexus, consent: noPurposeOrVendorConsentAndPubRestrictsNone, allowSync: false, }, { description: "Sync blocked by publisher - p1 enabled with p1 vendor exception, pub restricts p1 for vendor", - p1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + p1VendorExceptionMap: map[string]struct{}{appnexus: {}}, bidder: openrtb_ext.BidderAppnexus, consent: noPurposeOrVendorConsentAndPubRestrictsP1, allowSync: false, }, { description: "Sync allowed by vendor exception - p1 enabled with p1 vendor exception, pub restricts none", - p1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + p1VendorExceptionMap: map[string]struct{}{appnexus: {}}, bidder: openrtb_ext.BidderAppnexus, consent: noPurposeOrVendorConsentAndPubRestrictsNone, allowSync: true, diff --git a/gdpr/purpose_config.go b/gdpr/purpose_config.go index 015f23269ef..020d0aaa06e 100644 --- a/gdpr/purpose_config.go +++ b/gdpr/purpose_config.go @@ -2,8 +2,7 @@ package gdpr import ( "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/config" ) // purposeConfig represents all of the config info selected from the host and account configs for @@ -13,26 +12,26 @@ type purposeConfig struct { EnforceAlgo config.TCF2EnforcementAlgo EnforcePurpose bool EnforceVendors bool - VendorExceptionMap map[openrtb_ext.BidderName]struct{} + VendorExceptionMap map[string]struct{} BasicEnforcementVendorsMap map[string]struct{} } -// basicEnforcementVendor returns true if a given bidder is configured as a basic enforcement vendor +// basicEnforcementVendor returns true if a given bidder/analytics adapter is configured as a basic enforcement vendor // for the purpose -func (pc *purposeConfig) basicEnforcementVendor(bidder openrtb_ext.BidderName) bool { +func (pc *purposeConfig) basicEnforcementVendor(name string) bool { if pc.BasicEnforcementVendorsMap == nil { return false } - _, found := pc.BasicEnforcementVendorsMap[string(bidder)] + _, found := pc.BasicEnforcementVendorsMap[name] return found } -// vendorException returns true if a given bidder is configured as a vendor exception +// vendorException returns true if a given bidder/analytics adapter is configured as a vendor exception // for the purpose -func (pc *purposeConfig) vendorException(bidder openrtb_ext.BidderName) bool { +func (pc *purposeConfig) vendorException(name string) bool { if pc.VendorExceptionMap == nil { return false } - _, found := pc.VendorExceptionMap[bidder] + _, found := pc.VendorExceptionMap[name] return found } diff --git a/gdpr/purpose_config_test.go b/gdpr/purpose_config_test.go index e80733cc8ca..61b7b9b2d68 100644 --- a/gdpr/purpose_config_test.go +++ b/gdpr/purpose_config_test.go @@ -3,65 +3,72 @@ package gdpr import ( "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) func TestPurposeConfigBasicEnforcementVendor(t *testing.T) { + var ( + appnexus = string(openrtb_ext.BidderAppnexus) + ix = string(openrtb_ext.BidderIx) + pubmatic = string(openrtb_ext.BidderPubmatic) + rubicon = string(openrtb_ext.BidderRubicon) + ) + tests := []struct { description string giveBasicVendors map[string]struct{} - giveBidder openrtb_ext.BidderName + giveBidder string wantFound bool }{ { description: "vendor map is nil", giveBasicVendors: nil, - giveBidder: openrtb_ext.BidderAppnexus, + giveBidder: appnexus, wantFound: false, }, { description: "vendor map is empty", giveBasicVendors: map[string]struct{}{}, - giveBidder: openrtb_ext.BidderAppnexus, + giveBidder: appnexus, wantFound: false, }, { description: "vendor map has one bidders - bidder not found", giveBasicVendors: map[string]struct{}{ - string(openrtb_ext.BidderPubmatic): {}, + pubmatic: {}, }, - giveBidder: openrtb_ext.BidderAppnexus, + giveBidder: appnexus, wantFound: false, }, { description: "vendor map has one bidders - bidder found", giveBasicVendors: map[string]struct{}{ - string(openrtb_ext.BidderAppnexus): {}, + appnexus: {}, }, - giveBidder: openrtb_ext.BidderAppnexus, + giveBidder: appnexus, wantFound: true, }, { description: "vendor map has many bidderss - bidder not found", giveBasicVendors: map[string]struct{}{ - string(openrtb_ext.BidderIx): {}, - string(openrtb_ext.BidderPubmatic): {}, - string(openrtb_ext.BidderRubicon): {}, + ix: {}, + pubmatic: {}, + rubicon: {}, }, - giveBidder: openrtb_ext.BidderAppnexus, + giveBidder: appnexus, wantFound: false, }, { description: "vendor map has many bidderss - bidder found", giveBasicVendors: map[string]struct{}{ - string(openrtb_ext.BidderIx): {}, - string(openrtb_ext.BidderPubmatic): {}, - string(openrtb_ext.BidderAppnexus): {}, - string(openrtb_ext.BidderRubicon): {}, + ix: {}, + pubmatic: {}, + appnexus: {}, + rubicon: {}, }, - giveBidder: openrtb_ext.BidderAppnexus, + giveBidder: appnexus, wantFound: true, }, } @@ -77,59 +84,66 @@ func TestPurposeConfigBasicEnforcementVendor(t *testing.T) { } func TestPurposeConfigVendorException(t *testing.T) { + var ( + appnexus = string(openrtb_ext.BidderAppnexus) + ix = string(openrtb_ext.BidderIx) + pubmatic = string(openrtb_ext.BidderPubmatic) + rubicon = string(openrtb_ext.BidderRubicon) + ) + tests := []struct { description string - giveExceptions map[openrtb_ext.BidderName]struct{} - giveBidder openrtb_ext.BidderName + giveExceptions map[string]struct{} + giveBidder string wantFound bool }{ { description: "vendor exception map is nil", giveExceptions: nil, - giveBidder: openrtb_ext.BidderAppnexus, + giveBidder: appnexus, wantFound: false, }, { description: "vendor exception map is empty", - giveExceptions: map[openrtb_ext.BidderName]struct{}{}, - giveBidder: openrtb_ext.BidderAppnexus, + giveExceptions: map[string]struct{}{}, + giveBidder: appnexus, wantFound: false, }, { description: "vendor exception map has one bidders - bidder not found", - giveExceptions: map[openrtb_ext.BidderName]struct{}{ - openrtb_ext.BidderPubmatic: {}, + giveExceptions: map[string]struct{}{ + pubmatic: {}, }, - giveBidder: openrtb_ext.BidderAppnexus, + giveBidder: appnexus, wantFound: false, }, { description: "vendor exception map has one bidders - bidder found", - giveExceptions: map[openrtb_ext.BidderName]struct{}{ - openrtb_ext.BidderAppnexus: {}, + giveExceptions: map[string]struct{}{ + appnexus: {}, }, - giveBidder: openrtb_ext.BidderAppnexus, + giveBidder: appnexus, wantFound: true, }, { description: "vendor exception map has many bidderss - bidder not found", - giveExceptions: map[openrtb_ext.BidderName]struct{}{ - openrtb_ext.BidderIx: {}, - openrtb_ext.BidderPubmatic: {}, - openrtb_ext.BidderRubicon: {}, + giveExceptions: map[string]struct{}{ + ix: {}, + pubmatic: {}, + rubicon: {}, }, - giveBidder: openrtb_ext.BidderAppnexus, + giveBidder: appnexus, wantFound: false, }, { description: "vendor exception map has many bidderss - bidder found", - giveExceptions: map[openrtb_ext.BidderName]struct{}{ - openrtb_ext.BidderIx: {}, - openrtb_ext.BidderPubmatic: {}, - openrtb_ext.BidderAppnexus: {}, - openrtb_ext.BidderRubicon: {}, + giveExceptions: map[string]struct{}{ + ix: {}, + pubmatic: {}, + appnexus: {}, + rubicon: {}, }, - giveBidder: openrtb_ext.BidderAppnexus, + giveBidder: appnexus, wantFound: true, }, } diff --git a/gdpr/purpose_enforcer.go b/gdpr/purpose_enforcer.go index c8e76f988aa..5cc5d8e83d5 100644 --- a/gdpr/purpose_enforcer.go +++ b/gdpr/purpose_enforcer.go @@ -4,17 +4,17 @@ import ( "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/consentconstants" tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // PurposeEnforcer represents the enforcement strategy for determining if legal basis is achieved for a purpose type PurposeEnforcer interface { - LegalBasis(vendorInfo VendorInfo, bidder openrtb_ext.BidderName, consent tcf2.ConsentMetadata, overrides Overrides) bool + LegalBasis(vendorInfo VendorInfo, name string, consent tcf2.ConsentMetadata, overrides Overrides) bool } // PurposeEnforcerBuilder generates an instance of PurposeEnforcer for a given purpose and bidder -type PurposeEnforcerBuilder func(p consentconstants.Purpose, bidder openrtb_ext.BidderName) PurposeEnforcer +type PurposeEnforcerBuilder func(p consentconstants.Purpose, name string) PurposeEnforcer // Overrides specifies enforcement algorithm rule adjustments type Overrides struct { @@ -45,7 +45,7 @@ type PurposeEnforcers struct { func NewPurposeEnforcerBuilder(cfg TCF2ConfigReader) PurposeEnforcerBuilder { cachedEnforcers := make([]PurposeEnforcers, 10) - return func(purpose consentconstants.Purpose, bidder openrtb_ext.BidderName) PurposeEnforcer { + return func(purpose consentconstants.Purpose, name string) PurposeEnforcer { index := purpose - 1 var basicEnforcementVendor bool @@ -53,7 +53,7 @@ func NewPurposeEnforcerBuilder(cfg TCF2ConfigReader) PurposeEnforcerBuilder { basicEnforcementVendor = false } else { basicEnforcementVendors := cfg.BasicEnforcementVendors() - _, basicEnforcementVendor = basicEnforcementVendors[string(bidder)] + _, basicEnforcementVendor = basicEnforcementVendors[name] } enforceAlgo := cfg.PurposeEnforcementAlgo(purpose) diff --git a/gdpr/purpose_enforcer_test.go b/gdpr/purpose_enforcer_test.go index ea2075d9c65..756bb07d716 100644 --- a/gdpr/purpose_enforcer_test.go +++ b/gdpr/purpose_enforcer_test.go @@ -4,22 +4,24 @@ import ( "testing" "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) func TestNewPurposeEnforcerBuilder(t *testing.T) { + appnexus := string(openrtb_ext.BidderAppnexus) + tests := []struct { description string enforceAlgo config.TCF2EnforcementAlgo enforcePurpose bool enforceVendors bool basicVendorsMap map[string]struct{} - vendorExceptionMap map[openrtb_ext.BidderName]struct{} + vendorExceptionMap map[string]struct{} purpose consentconstants.Purpose - bidder openrtb_ext.BidderName + bidder string wantType PurposeEnforcer }{ { @@ -28,9 +30,9 @@ func TestNewPurposeEnforcerBuilder(t *testing.T) { enforcePurpose: true, enforceVendors: true, basicVendorsMap: map[string]struct{}{}, - vendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + vendorExceptionMap: map[string]struct{}{}, purpose: consentconstants.Purpose(1), - bidder: openrtb_ext.BidderAppnexus, + bidder: appnexus, wantType: &FullEnforcement{}, }, { @@ -38,10 +40,10 @@ func TestNewPurposeEnforcerBuilder(t *testing.T) { enforceAlgo: config.TCF2FullEnforcement, enforcePurpose: true, enforceVendors: true, - basicVendorsMap: map[string]struct{}{string(openrtb_ext.BidderAppnexus): {}}, - vendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + basicVendorsMap: map[string]struct{}{appnexus: {}}, + vendorExceptionMap: map[string]struct{}{}, purpose: consentconstants.Purpose(1), - bidder: openrtb_ext.BidderAppnexus, + bidder: appnexus, wantType: &FullEnforcement{}, }, { @@ -50,9 +52,9 @@ func TestNewPurposeEnforcerBuilder(t *testing.T) { enforcePurpose: true, enforceVendors: true, basicVendorsMap: map[string]struct{}{}, - vendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + vendorExceptionMap: map[string]struct{}{}, purpose: consentconstants.Purpose(1), - bidder: openrtb_ext.BidderAppnexus, + bidder: appnexus, wantType: &BasicEnforcement{}, }, { @@ -61,9 +63,9 @@ func TestNewPurposeEnforcerBuilder(t *testing.T) { enforcePurpose: true, enforceVendors: true, basicVendorsMap: map[string]struct{}{}, - vendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + vendorExceptionMap: map[string]struct{}{}, purpose: consentconstants.Purpose(2), - bidder: openrtb_ext.BidderAppnexus, + bidder: appnexus, wantType: &FullEnforcement{}, }, { @@ -71,10 +73,10 @@ func TestNewPurposeEnforcerBuilder(t *testing.T) { enforceAlgo: config.TCF2FullEnforcement, enforcePurpose: true, enforceVendors: true, - basicVendorsMap: map[string]struct{}{string(openrtb_ext.BidderAppnexus): {}}, - vendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + basicVendorsMap: map[string]struct{}{appnexus: {}}, + vendorExceptionMap: map[string]struct{}{}, purpose: consentconstants.Purpose(2), - bidder: openrtb_ext.BidderAppnexus, + bidder: appnexus, wantType: &BasicEnforcement{}, }, { @@ -83,9 +85,9 @@ func TestNewPurposeEnforcerBuilder(t *testing.T) { enforcePurpose: true, enforceVendors: true, basicVendorsMap: map[string]struct{}{}, - vendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + vendorExceptionMap: map[string]struct{}{}, purpose: consentconstants.Purpose(2), - bidder: openrtb_ext.BidderAppnexus, + bidder: appnexus, wantType: &BasicEnforcement{}, }, } @@ -109,10 +111,10 @@ func TestNewPurposeEnforcerBuilder(t *testing.T) { assert.IsType(t, tt.wantType, enforcer1, tt.description) // assert enforcer 1 config values are properly set - switch enforcer1.(type) { + switch enforcerCasted := enforcer1.(type) { case *FullEnforcement: { - fullEnforcer := enforcer1.(*FullEnforcement) + fullEnforcer := enforcerCasted assert.Equal(t, fullEnforcer.cfg.PurposeID, tt.purpose, tt.description) assert.Equal(t, fullEnforcer.cfg.EnforceAlgo, tt.enforceAlgo, tt.description) assert.Equal(t, fullEnforcer.cfg.EnforcePurpose, tt.enforcePurpose, tt.description) @@ -130,19 +132,21 @@ func TestNewPurposeEnforcerBuilder(t *testing.T) { assert.Equal(t, basicEnforcer.cfg.BasicEnforcementVendorsMap, tt.basicVendorsMap, tt.description) assert.Equal(t, basicEnforcer.cfg.VendorExceptionMap, tt.vendorExceptionMap, tt.description) } + default: + assert.FailNow(t, "unexpected type of enforcer") } } } func TestNewPurposeEnforcerBuilderCaching(t *testing.T) { - bidder1 := openrtb_ext.BidderAppnexus + bidder1 := string(openrtb_ext.BidderAppnexus) bidder1Enforcers := make([]PurposeEnforcer, 11) - bidder2 := openrtb_ext.BidderIx + bidder2 := string(openrtb_ext.BidderIx) bidder2Enforcers := make([]PurposeEnforcer, 11) - bidder3 := openrtb_ext.BidderPubmatic + bidder3 := string(openrtb_ext.BidderPubmatic) bidder3Enforcers := make([]PurposeEnforcer, 11) - bidder4 := openrtb_ext.BidderRubicon + bidder4 := string(openrtb_ext.BidderRubicon) bidder4Enforcers := make([]PurposeEnforcer, 11) cfg := fakeTCF2ConfigReader{ @@ -198,7 +202,7 @@ type fakeTCF2ConfigReader struct { enforceAlgo config.TCF2EnforcementAlgo enforcePurpose bool enforceVendors bool - vendorExceptionMap map[openrtb_ext.BidderName]struct{} + vendorExceptionMap map[string]struct{} basicEnforcementVendorsMap map[string]struct{} } @@ -226,7 +230,7 @@ func (fcr *fakeTCF2ConfigReader) PurposeEnforcementAlgo(purpose consentconstants func (fcr *fakeTCF2ConfigReader) PurposeEnforcingVendors(purpose consentconstants.Purpose) bool { return fcr.enforceVendors } -func (fcr *fakeTCF2ConfigReader) PurposeVendorExceptions(purpose consentconstants.Purpose) map[openrtb_ext.BidderName]struct{} { +func (fcr *fakeTCF2ConfigReader) PurposeVendorExceptions(purpose consentconstants.Purpose) map[string]struct{} { return fcr.vendorExceptionMap } func (fcr *fakeTCF2ConfigReader) PurposeOneTreatmentEnabled() bool { diff --git a/gdpr/signal.go b/gdpr/signal.go index ed7fe1dd8ea..478985b64f5 100644 --- a/gdpr/signal.go +++ b/gdpr/signal.go @@ -3,7 +3,7 @@ package gdpr import ( "strconv" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v3/errortypes" ) type Signal int diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go index da6bdbae415..dc035483479 100644 --- a/gdpr/vendorlist-fetching.go +++ b/gdpr/vendorlist-fetching.go @@ -14,7 +14,7 @@ import ( "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/go-gdpr/vendorlist2" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v3/config" "golang.org/x/net/context/ctxhttp" ) diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go index b8c73bbf9a4..4e9e31dd936 100644 --- a/gdpr/vendorlist-fetching_test.go +++ b/gdpr/vendorlist-fetching_test.go @@ -11,8 +11,8 @@ import ( "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/consentconstants" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) func TestFetcherDynamicLoadListExists(t *testing.T) { diff --git a/go.mod b/go.mod index c61177ef39a..e844fe5bf25 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,14 @@ -module github.com/prebid/prebid-server +module github.com/prebid/prebid-server/v3 -go 1.20 +go 1.21 + +retract v3.0.0 // Forgot to update major version in import path and module name require ( github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/IABTechLab/adscert v0.34.0 github.com/NYTimes/gziphandler v1.1.1 + github.com/alitto/pond v1.8.3 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/benbjohnson/clock v1.3.0 github.com/buger/jsonparser v1.1.1 @@ -14,46 +17,47 @@ require ( github.com/docker/go-units v0.4.0 github.com/go-sql-driver/mysql v1.6.0 github.com/gofrs/uuid v4.2.0+incompatible - github.com/golang/glog v1.0.0 + github.com/golang/glog v1.1.0 + github.com/google/go-cmp v0.6.0 + github.com/json-iterator/go v1.1.12 github.com/julienschmidt/httprouter v1.3.0 github.com/lib/pq v1.10.4 github.com/mitchellh/copystructure v1.2.0 + github.com/modern-go/reflect2 v1.0.2 github.com/pkg/errors v0.9.1 github.com/prebid/go-gdpr v1.12.0 - github.com/prebid/go-gpp v0.1.1 - github.com/prebid/openrtb/v17 v17.1.0 - github.com/prebid/openrtb/v19 v19.0.0 + github.com/prebid/go-gpp v0.2.0 + github.com/prebid/openrtb/v20 v20.1.0 github.com/prometheus/client_golang v1.12.1 github.com/prometheus/client_model v0.2.0 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 - github.com/rs/cors v1.8.2 + github.com/rs/cors v1.11.0 github.com/spf13/viper v1.12.0 github.com/stretchr/testify v1.8.1 github.com/vrischmann/go-metrics-influxdb v0.1.1 github.com/xeipuuv/gojsonschema v1.2.0 github.com/yudai/gojsondiff v1.0.0 - golang.org/x/net v0.7.0 - golang.org/x/text v0.7.0 - google.golang.org/grpc v1.53.0 + golang.org/x/net v0.23.0 + golang.org/x/text v0.14.0 + google.golang.org/grpc v1.56.3 gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/yaml.v3 v3.0.1 ) require ( + github.com/51Degrees/device-detection-go/v4 v4.4.35 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d // indirect - github.com/json-iterator/go v1.1.12 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -66,14 +70,18 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/subosito/gotenv v1.3.0 // indirect + github.com/tidwall/gjson v1.17.1 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/sjson v1.2.5 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/yudai/pp v2.0.1+incompatible // indirect - golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect - golang.org/x/sys v0.5.0 // indirect - google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect - google.golang.org/protobuf v1.28.1 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/sys v0.18.0 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 00978875fe7..a6a6226c616 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/51Degrees/device-detection-go/v4 v4.4.35 h1:qhP2tzoXhGE1aYY3NftMJ+ccxz0+2kM8aF4SH7fTyuA= +github.com/51Degrees/device-detection-go/v4 v4.4.35/go.mod h1:dbdG1fySqdY+a5pUnZ0/G0eD03G6H3Vh8kRC+1f9qSc= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= @@ -64,6 +66,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alitto/pond v1.8.3 h1:ydIqygCLVPqIX/USe5EaV/aSRXTRXDEI9JwuDdu+/xs= +github.com/alitto/pond v1.8.3/go.mod h1:CmvIIGd5jKLasGI3D87qDkQxjzChdKMmnXMg3fG6M6Q= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -133,6 +137,7 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= @@ -160,8 +165,8 @@ github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -191,8 +196,9 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -210,7 +216,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -312,9 +319,11 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= @@ -397,12 +406,10 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prebid/go-gdpr v1.12.0 h1:OrjQ7Uc+lCRYaOirQ48jjG/PBMvZsKNAaRTgzxN6iZ0= github.com/prebid/go-gdpr v1.12.0/go.mod h1:mPZAdkRxn+iuSjaUuJAi9+0SppBOdM1PCzv/55UH3pY= -github.com/prebid/go-gpp v0.1.1 h1:uTMJ+eHmKWL9WvDuxFT4LDoOeJW1yOsfWITqi49ZuY0= -github.com/prebid/go-gpp v0.1.1/go.mod h1:b0TLoVln+HXFD9L9xeimxIH3FN8WDKPJ42auslxEkow= -github.com/prebid/openrtb/v17 v17.1.0 h1:sFdufdVv9zuoDLuo2/I863lSP9QlEqtZZQyDz5OXPhY= -github.com/prebid/openrtb/v17 v17.1.0/go.mod h1:nMj7j6aTIopCG91Wv3nuzcFTc7YRSOzuzdPxal+FY50= -github.com/prebid/openrtb/v19 v19.0.0 h1:NA7okrg7KcvL5wEg6yI0mAyujpyfkC8XSQr3h5ocN88= -github.com/prebid/openrtb/v19 v19.0.0/go.mod h1:jK+/g4Dh5vOnNl0Nh7isbZlub29aJYyrtoBkjmhzTIg= +github.com/prebid/go-gpp v0.2.0 h1:41Ssxd4Zxr50WgwG1q/1+6awGU3pFnwV7FR4XCLQSuM= +github.com/prebid/go-gpp v0.2.0/go.mod h1:b0TLoVln+HXFD9L9xeimxIH3FN8WDKPJ42auslxEkow= +github.com/prebid/openrtb/v20 v20.1.0 h1:Rb+Z3H3UxiqqnjgJK3R9Wt73ibrh7HPzG7ikBckQNqc= +github.com/prebid/openrtb/v20 v20.1.0/go.mod h1:hLBrA/APkSrxs5MaW639l+y/EAHivDfRagO2TX/wbSc= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= @@ -434,8 +441,9 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqn github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= -github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= -github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= +github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -481,6 +489,15 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= +github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/vrischmann/go-metrics-influxdb v0.1.1 h1:xneKFRjsS4BiVYvAKaM/rOlXYd1pGHksnES0ECCJLgo= github.com/vrischmann/go-metrics-influxdb v0.1.1/go.mod h1:q7YC8bFETCYopXRMtUvQQdLaoVhpsEwvQS2zZEYCqg8= @@ -529,8 +546,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -615,8 +632,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -727,8 +744,8 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -741,8 +758,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -913,8 +930,8 @@ google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -945,8 +962,8 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -961,8 +978,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/hooks/empty_plan.go b/hooks/empty_plan.go index 01e01843324..72c1a9276d5 100644 --- a/hooks/empty_plan.go +++ b/hooks/empty_plan.go @@ -1,8 +1,8 @@ package hooks import ( - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/hooks/hookstage" ) // EmptyPlanBuilder implements the ExecutionPlanBuilder interface diff --git a/hooks/hookanalytics/analytics_test.go b/hooks/hookanalytics/analytics_test.go index abf5e7f2ddc..a1186e1b862 100644 --- a/hooks/hookanalytics/analytics_test.go +++ b/hooks/hookanalytics/analytics_test.go @@ -3,7 +3,7 @@ package hookanalytics import ( "testing" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/hooks/hookexecution/context.go b/hooks/hookexecution/context.go index 5f7cc3ab188..98a38714618 100644 --- a/hooks/hookexecution/context.go +++ b/hooks/hookexecution/context.go @@ -4,17 +4,19 @@ import ( "sync" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/hooks/hookstage" + "github.com/prebid/prebid-server/v3/privacy" ) // executionContext holds information passed to module's hook during hook execution. type executionContext struct { - endpoint string - stage string - accountId string - account *config.Account - moduleContexts *moduleContexts + endpoint string + stage string + accountID string + account *config.Account + moduleContexts *moduleContexts + activityControl privacy.ActivityControl } func (ctx executionContext) getModuleContext(moduleName string) hookstage.ModuleInvocationContext { diff --git a/hooks/hookexecution/enricher.go b/hooks/hookexecution/enricher.go index c3dd5a23339..7ca9f65f176 100644 --- a/hooks/hookexecution/enricher.go +++ b/hooks/hookexecution/enricher.go @@ -4,9 +4,9 @@ import ( "encoding/json" "github.com/buger/jsonparser" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/util/jsonutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" ) diff --git a/hooks/hookexecution/enricher_test.go b/hooks/hookexecution/enricher_test.go index 24f0f1c1d45..beb07444d10 100644 --- a/hooks/hookexecution/enricher_test.go +++ b/hooks/hookexecution/enricher_test.go @@ -6,11 +6,11 @@ import ( "os" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookanalytics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/hooks/hookanalytics" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/hooks/hookexecution/errors.go b/hooks/hookexecution/errors.go index b1cf912ccee..0e61ebc983b 100644 --- a/hooks/hookexecution/errors.go +++ b/hooks/hookexecution/errors.go @@ -3,7 +3,7 @@ package hookexecution import ( "fmt" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v3/errortypes" ) // TimeoutError indicates exceeding of the max execution time allotted for hook. diff --git a/hooks/hookexecution/execution.go b/hooks/hookexecution/execution.go index 18c927896b9..caaba59c3bb 100644 --- a/hooks/hookexecution/execution.go +++ b/hooks/hookexecution/execution.go @@ -7,9 +7,14 @@ import ( "sync" "time" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/hooks" + "github.com/prebid/prebid-server/v3/hooks/hookstage" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/ortb" + "github.com/prebid/prebid-server/v3/privacy" + "github.com/prebid/prebid-server/v3/util/iputil" ) type hookResponse[T any] struct { @@ -66,10 +71,11 @@ func executeGroup[H any, P any]( for _, hook := range group.Hooks { mCtx := executionCtx.getModuleContext(hook.Module) + newPayload := handleModuleActivities(hook.Code, executionCtx.activityControl, payload, executionCtx.account) wg.Add(1) go func(hw hooks.HookWrapper[H], moduleCtx hookstage.ModuleInvocationContext) { defer wg.Done() - executeHook(moduleCtx, hw, payload, hookHandler, group.Timeout, resp, rejected) + executeHook(moduleCtx, hw, newPayload, hookHandler, group.Timeout, resp, rejected) }(hook, mCtx) } @@ -176,7 +182,7 @@ func handleHookResponse[P any]( metricEngine metrics.MetricsEngine, ) (P, HookOutcome, *RejectError) { var rejectErr *RejectError - labels := metrics.ModuleLabels{Module: moduleReplacer.Replace(hr.HookID.ModuleCode), Stage: ctx.stage, AccountID: ctx.accountId} + labels := metrics.ModuleLabels{Module: moduleReplacer.Replace(hr.HookID.ModuleCode), Stage: ctx.stage, AccountID: ctx.accountID} metricEngine.RecordModuleCalled(labels, hr.ExecutionTime) hookOutcome := HookOutcome{ @@ -311,3 +317,47 @@ func handleHookMutations[P any]( return payload } + +func handleModuleActivities[P any](hookCode string, activityControl privacy.ActivityControl, payload P, account *config.Account) P { + payloadData, ok := any(&payload).(hookstage.RequestUpdater) + if !ok { + return payload + } + + scopeGeneral := privacy.Component{Type: privacy.ComponentTypeGeneral, Name: hookCode} + transmitUserFPDActivityAllowed := activityControl.Allow(privacy.ActivityTransmitUserFPD, scopeGeneral, privacy.ActivityRequest{}) + transmitPreciseGeoActivityAllowed := activityControl.Allow(privacy.ActivityTransmitPreciseGeo, scopeGeneral, privacy.ActivityRequest{}) + + if transmitUserFPDActivityAllowed && transmitPreciseGeoActivityAllowed { + return payload + } + + // changes need to be applied to new payload and leave original payload unchanged + bidderReq := payloadData.GetBidderRequestPayload() + + bidderReqCopy := &openrtb_ext.RequestWrapper{ + BidRequest: ortb.CloneBidRequestPartial(bidderReq.BidRequest), + } + + if !transmitUserFPDActivityAllowed { + privacy.ScrubUserFPD(bidderReqCopy) + } + if !transmitPreciseGeoActivityAllowed { + var ipConf privacy.IPConf + if account != nil { + ipConf = privacy.IPConf{IPV6: account.Privacy.IPv6Config, IPV4: account.Privacy.IPv4Config} + } else { + ipConf = privacy.IPConf{ + IPV6: config.IPv6{AnonKeepBits: iputil.IPv6DefaultMaskingBitSize}, + IPV4: config.IPv4{AnonKeepBits: iputil.IPv4DefaultMaskingBitSize}} + } + + privacy.ScrubGeoAndDeviceIP(bidderReqCopy, ipConf) + } + + var newPayload = payload + var np = any(&newPayload).(hookstage.RequestUpdater) + np.SetBidderRequestPayload(bidderReqCopy) + return newPayload + +} diff --git a/hooks/hookexecution/execution_test.go b/hooks/hookexecution/execution_test.go new file mode 100644 index 00000000000..1090db578d4 --- /dev/null +++ b/hooks/hookexecution/execution_test.go @@ -0,0 +1,235 @@ +package hookexecution + +import ( + "testing" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/hooks/hookstage" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/privacy" + "github.com/stretchr/testify/assert" +) + +const ( + testIpv6 = "1111:2222:3333:4444:5555:6666:7777:8888" + testIPv6Scrubbed = "1111:2222::" + testIPv6ScrubbedDefault = "1111:2222:3333:4400::" + testIPv6ScrubBytes = 32 +) + +func TestHandleModuleActivitiesBidderRequestPayload(t *testing.T) { + + testCases := []struct { + description string + hookCode string + privacyConfig *config.AccountPrivacy + inPayloadData hookstage.BidderRequestPayload + expectedPayloadData hookstage.BidderRequestPayload + }{ + { + description: "payload should change when userFPD is blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.BidderRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: "test_user_id"}, + }}, + }, + privacyConfig: getTransmitUFPDActivityConfig("foo", false), + expectedPayloadData: hookstage.BidderRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: ""}, + }, + }}, + }, + { + description: "payload should not change when userFPD is not blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.BidderRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: "test_user_id"}, + }}, + }, + privacyConfig: getTransmitUFPDActivityConfig("foo", true), + expectedPayloadData: hookstage.BidderRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: "test_user_id"}, + }}, + }, + }, + { + description: "payload should change when transmitPreciseGeo is blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.BidderRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{IPv6: testIpv6}, + }}, + }, + privacyConfig: getTransmitPreciseGeoActivityConfig("foo", false), + expectedPayloadData: hookstage.BidderRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{IPv6: testIPv6ScrubbedDefault}, + }, + }}, + }, + { + description: "payload should not change when transmitPreciseGeo is not blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.BidderRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{IPv6: testIpv6}, + }}, + }, + privacyConfig: getTransmitPreciseGeoActivityConfig("foo", true), + expectedPayloadData: hookstage.BidderRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{IPv6: testIpv6}, + }}, + }, + }, + } + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + //check input payload didn't change + origInPayloadData := test.inPayloadData + activityControl := privacy.NewActivityControl(test.privacyConfig) + newPayload := handleModuleActivities(test.hookCode, activityControl, test.inPayloadData, nil) + assert.Equal(t, test.expectedPayloadData.Request.BidRequest, newPayload.Request.BidRequest) + assert.Equal(t, origInPayloadData, test.inPayloadData) + }) + } +} + +func TestHandleModuleActivitiesProcessedAuctionRequestPayload(t *testing.T) { + + testCases := []struct { + description string + hookCode string + privacyConfig *config.AccountPrivacy + inPayloadData hookstage.ProcessedAuctionRequestPayload + expectedPayloadData hookstage.ProcessedAuctionRequestPayload + }{ + { + description: "payload should change when userFPD is blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.ProcessedAuctionRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: "test_user_id"}, + }}, + }, + privacyConfig: getTransmitUFPDActivityConfig("foo", false), + expectedPayloadData: hookstage.ProcessedAuctionRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: ""}, + }}, + }, + }, + { + description: "payload should not change when userFPD is not blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.ProcessedAuctionRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: "test_user_id"}, + }}, + }, + privacyConfig: getTransmitUFPDActivityConfig("foo", true), + expectedPayloadData: hookstage.ProcessedAuctionRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + User: &openrtb2.User{ID: "test_user_id"}, + }}, + }, + }, + + { + description: "payload should change when transmitPreciseGeo is blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.ProcessedAuctionRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{IPv6: testIpv6}, + }}, + }, + privacyConfig: getTransmitPreciseGeoActivityConfig("foo", false), + expectedPayloadData: hookstage.ProcessedAuctionRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{IPv6: testIPv6Scrubbed}, + }}, + }, + }, + { + description: "payload should not change when transmitPreciseGeo is not blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.ProcessedAuctionRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{IPv6: testIpv6}, + }}, + }, + privacyConfig: getTransmitPreciseGeoActivityConfig("foo", true), + expectedPayloadData: hookstage.ProcessedAuctionRequestPayload{ + Request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + Device: &openrtb2.Device{IPv6: testIpv6}, + }}, + }, + }, + } + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + //check input payload didn't change + origInPayloadData := test.inPayloadData + activityControl := privacy.NewActivityControl(test.privacyConfig) + account := &config.Account{Privacy: config.AccountPrivacy{IPv6Config: config.IPv6{AnonKeepBits: testIPv6ScrubBytes}}} + newPayload := handleModuleActivities(test.hookCode, activityControl, test.inPayloadData, account) + assert.Equal(t, test.expectedPayloadData.Request.BidRequest, newPayload.Request.BidRequest) + assert.Equal(t, origInPayloadData, test.inPayloadData) + }) + } +} + +func TestHandleModuleActivitiesNoBidderRequestPayload(t *testing.T) { + + testCases := []struct { + description string + hookCode string + privacyConfig *config.AccountPrivacy + inPayloadData hookstage.RawAuctionRequestPayload + expectedPayloadData hookstage.RawAuctionRequestPayload + }{ + { + description: "payload should not change when userFPD is blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.RawAuctionRequestPayload{}, + privacyConfig: getTransmitUFPDActivityConfig("foo", false), + expectedPayloadData: hookstage.RawAuctionRequestPayload{}, + }, + { + description: "payload should not change when userFPD is not blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.RawAuctionRequestPayload{}, + privacyConfig: getTransmitUFPDActivityConfig("foo", true), + expectedPayloadData: hookstage.RawAuctionRequestPayload{}, + }, + { + description: "payload should not change when transmitPreciseGeo is blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.RawAuctionRequestPayload{}, + privacyConfig: getTransmitPreciseGeoActivityConfig("foo", false), + expectedPayloadData: hookstage.RawAuctionRequestPayload{}, + }, + { + description: "payload should not change when transmitPreciseGeo is not blocked by activity", + hookCode: "foo", + inPayloadData: hookstage.RawAuctionRequestPayload{}, + privacyConfig: getTransmitPreciseGeoActivityConfig("foo", true), + expectedPayloadData: hookstage.RawAuctionRequestPayload{}, + }, + } + for _, test := range testCases { + t.Run(test.description, func(t *testing.T) { + //check input payload didn't change + origInPayloadData := test.inPayloadData + activityControl := privacy.NewActivityControl(test.privacyConfig) + newPayload := handleModuleActivities(test.hookCode, activityControl, test.inPayloadData, &config.Account{}) + assert.Equal(t, test.expectedPayloadData, newPayload) + assert.Equal(t, origInPayloadData, test.inPayloadData) + }) + } +} diff --git a/hooks/hookexecution/executor.go b/hooks/hookexecution/executor.go index 5074d4b9ab9..a1ff81f9714 100644 --- a/hooks/hookexecution/executor.go +++ b/hooks/hookexecution/executor.go @@ -5,14 +5,15 @@ import ( "net/http" "sync" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/hooks" + "github.com/prebid/prebid-server/v3/hooks/hookstage" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/privacy" ) const ( @@ -34,7 +35,7 @@ type StageExecutor interface { ExecuteEntrypointStage(req *http.Request, body []byte) ([]byte, *RejectError) ExecuteRawAuctionStage(body []byte) ([]byte, *RejectError) ExecuteProcessedAuctionStage(req *openrtb_ext.RequestWrapper) error - ExecuteBidderRequestStage(req *openrtb2.BidRequest, bidder string) *RejectError + ExecuteBidderRequestStage(req *openrtb_ext.RequestWrapper, bidder string) *RejectError ExecuteRawBidderResponseStage(response *adapters.BidderResponse, bidder string) *RejectError ExecuteAllProcessedBidResponsesStage(adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) ExecuteAuctionResponseStage(response *openrtb2.BidResponse) @@ -43,17 +44,19 @@ type StageExecutor interface { type HookStageExecutor interface { StageExecutor SetAccount(account *config.Account) + SetActivityControl(activityControl privacy.ActivityControl) GetOutcomes() []StageOutcome } type hookExecutor struct { - account *config.Account - accountID string - endpoint string - planBuilder hooks.ExecutionPlanBuilder - stageOutcomes []StageOutcome - moduleContexts *moduleContexts - metricEngine metrics.MetricsEngine + account *config.Account + accountID string + endpoint string + planBuilder hooks.ExecutionPlanBuilder + stageOutcomes []StageOutcome + moduleContexts *moduleContexts + metricEngine metrics.MetricsEngine + activityControl privacy.ActivityControl // Mutex needed for BidderRequest and RawBidderResponse Stages as they are run in several goroutines sync.Mutex } @@ -77,6 +80,10 @@ func (e *hookExecutor) SetAccount(account *config.Account) { e.accountID = account.ID } +func (e *hookExecutor) SetActivityControl(activityControl privacy.ActivityControl) { + e.activityControl = activityControl +} + func (e *hookExecutor) GetOutcomes() []StageOutcome { return e.stageOutcomes } @@ -160,7 +167,7 @@ func (e *hookExecutor) ExecuteProcessedAuctionStage(request *openrtb_ext.Request stageName := hooks.StageProcessedAuctionRequest.String() executionCtx := e.newContext(stageName) - payload := hookstage.ProcessedAuctionRequestPayload{RequestWrapper: request} + payload := hookstage.ProcessedAuctionRequestPayload{Request: request} outcome, _, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine) outcome.Entity = entityAuctionRequest @@ -177,7 +184,7 @@ func (e *hookExecutor) ExecuteProcessedAuctionStage(request *openrtb_ext.Request return reject } -func (e *hookExecutor) ExecuteBidderRequestStage(req *openrtb2.BidRequest, bidder string) *RejectError { +func (e *hookExecutor) ExecuteBidderRequestStage(req *openrtb_ext.RequestWrapper, bidder string) *RejectError { plan := e.planBuilder.PlanForBidderRequestStage(e.endpoint, e.account) if len(plan) == 0 { return nil @@ -194,8 +201,8 @@ func (e *hookExecutor) ExecuteBidderRequestStage(req *openrtb2.BidRequest, bidde stageName := hooks.StageBidderRequest.String() executionCtx := e.newContext(stageName) - payload := hookstage.BidderRequestPayload{BidRequest: req, Bidder: bidder} - outcome, payload, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine) + payload := hookstage.BidderRequestPayload{Request: req, Bidder: bidder} + outcome, _, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine) outcome.Entity = entity(bidder) outcome.Stage = stageName @@ -222,10 +229,10 @@ func (e *hookExecutor) ExecuteRawBidderResponseStage(response *adapters.BidderRe stageName := hooks.StageRawBidderResponse.String() executionCtx := e.newContext(stageName) - payload := hookstage.RawBidderResponsePayload{Bids: response.Bids, Bidder: bidder} + payload := hookstage.RawBidderResponsePayload{BidderResponse: response, Bidder: bidder} outcome, payload, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine) - response.Bids = payload.Bids + response = payload.BidderResponse outcome.Entity = entity(bidder) outcome.Stage = stageName @@ -290,11 +297,12 @@ func (e *hookExecutor) ExecuteAuctionResponseStage(response *openrtb2.BidRespons func (e *hookExecutor) newContext(stage string) executionContext { return executionContext{ - account: e.account, - accountId: e.accountID, - endpoint: e.endpoint, - moduleContexts: e.moduleContexts, - stage: stage, + account: e.account, + accountID: e.accountID, + endpoint: e.endpoint, + moduleContexts: e.moduleContexts, + stage: stage, + activityControl: e.activityControl, } } @@ -316,6 +324,8 @@ type EmptyHookExecutor struct{} func (executor EmptyHookExecutor) SetAccount(_ *config.Account) {} +func (executor EmptyHookExecutor) SetActivityControl(_ privacy.ActivityControl) {} + func (executor EmptyHookExecutor) GetOutcomes() []StageOutcome { return []StageOutcome{} } @@ -332,7 +342,7 @@ func (executor EmptyHookExecutor) ExecuteProcessedAuctionStage(_ *openrtb_ext.Re return nil } -func (executor EmptyHookExecutor) ExecuteBidderRequestStage(_ *openrtb2.BidRequest, bidder string) *RejectError { +func (executor EmptyHookExecutor) ExecuteBidderRequestStage(_ *openrtb_ext.RequestWrapper, bidder string) *RejectError { return nil } diff --git a/hooks/hookexecution/executor_test.go b/hooks/hookexecution/executor_test.go index 68b990bb595..5e19f24a96f 100644 --- a/hooks/hookexecution/executor_test.go +++ b/hooks/hookexecution/executor_test.go @@ -8,23 +8,24 @@ import ( "testing" "time" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookanalytics" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/metrics" - metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/hooks" + "github.com/prebid/prebid-server/v3/hooks/hookanalytics" + "github.com/prebid/prebid-server/v3/hooks/hookstage" + "github.com/prebid/prebid-server/v3/metrics" + metricsConfig "github.com/prebid/prebid-server/v3/metrics/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/privacy" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) func TestEmptyHookExecutor(t *testing.T) { executor := EmptyHookExecutor{} - executor.SetAccount(&config.Account{}) body := []byte(`{"foo": "bar"}`) reader := bytes.NewReader(body) @@ -37,7 +38,7 @@ func TestEmptyHookExecutor(t *testing.T) { entrypointBody, entrypointRejectErr := executor.ExecuteEntrypointStage(req, body) rawAuctionBody, rawAuctionRejectErr := executor.ExecuteRawAuctionStage(body) processedAuctionRejectErr := executor.ExecuteProcessedAuctionStage(&openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}}) - bidderRequestRejectErr := executor.ExecuteBidderRequestStage(bidderRequest, "bidder-name") + bidderRequestRejectErr := executor.ExecuteBidderRequestStage(&openrtb_ext.RequestWrapper{BidRequest: bidderRequest}, "bidder-name") executor.ExecuteAuctionResponseStage(&openrtb2.BidResponse{}) outcomes := executor.GetOutcomes() @@ -417,14 +418,12 @@ func TestExecuteRawAuctionStage(t *testing.T) { const urlString string = "https://prebid.com/openrtb2/auction" foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} - account := &config.Account{} testCases := []struct { description string givenBody string givenUrl string givenPlanBuilder hooks.ExecutionPlanBuilder - givenAccount *config.Account expectedBody string expectedReject *RejectError expectedModuleContexts *moduleContexts @@ -435,7 +434,6 @@ func TestExecuteRawAuctionStage(t *testing.T) { givenBody: body, givenUrl: urlString, givenPlanBuilder: hooks.EmptyPlanBuilder{}, - givenAccount: account, expectedBody: body, expectedReject: nil, expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, @@ -446,7 +444,6 @@ func TestExecuteRawAuctionStage(t *testing.T) { givenBody: body, givenUrl: urlString, givenPlanBuilder: TestApplyHookMutationsBuilder{}, - givenAccount: account, expectedBody: bodyUpdated, expectedReject: nil, expectedModuleContexts: foobarModuleCtx, @@ -505,7 +502,6 @@ func TestExecuteRawAuctionStage(t *testing.T) { givenBody: body, givenUrl: urlString, givenPlanBuilder: TestRejectPlanBuilder{}, - givenAccount: nil, expectedBody: bodyUpdated, expectedReject: &RejectError{0, HookID{ModuleCode: "foobar", HookImplCode: "bar"}, hooks.StageRawAuctionRequest.String()}, expectedModuleContexts: foobarModuleCtx, @@ -566,7 +562,6 @@ func TestExecuteRawAuctionStage(t *testing.T) { givenBody: body, givenUrl: urlString, givenPlanBuilder: TestWithTimeoutPlanBuilder{}, - givenAccount: account, expectedBody: bodyUpdated, expectedReject: nil, expectedModuleContexts: foobarModuleCtx, @@ -615,7 +610,6 @@ func TestExecuteRawAuctionStage(t *testing.T) { givenBody: body, givenUrl: urlString, givenPlanBuilder: TestWithModuleContextsPlanBuilder{}, - givenAccount: account, expectedBody: body, expectedReject: nil, expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ @@ -674,7 +668,10 @@ func TestExecuteRawAuctionStage(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(t *testing.T) { exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) - exec.SetAccount(test.givenAccount) + + privacyConfig := getModuleActivities("foo", false, false) + ac := privacy.NewActivityControl(privacyConfig) + exec.SetActivityControl(ac) newBody, reject := exec.ExecuteRawAuctionStage([]byte(test.givenBody)) @@ -694,14 +691,12 @@ func TestExecuteRawAuctionStage(t *testing.T) { func TestExecuteProcessedAuctionStage(t *testing.T) { foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} - account := &config.Account{} req := openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id"}} reqUpdated := openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id", Yob: 2000, Consent: "true"}} testCases := []struct { description string givenPlanBuilder hooks.ExecutionPlanBuilder - givenAccount *config.Account givenRequest openrtb_ext.RequestWrapper expectedRequest openrtb2.BidRequest expectedErr error @@ -711,7 +706,6 @@ func TestExecuteProcessedAuctionStage(t *testing.T) { { description: "Request not changed if hook execution plan empty", givenPlanBuilder: hooks.EmptyPlanBuilder{}, - givenAccount: account, givenRequest: openrtb_ext.RequestWrapper{BidRequest: &req}, expectedRequest: req, expectedErr: nil, @@ -721,7 +715,6 @@ func TestExecuteProcessedAuctionStage(t *testing.T) { { description: "Request changed if hooks return mutations", givenPlanBuilder: TestApplyHookMutationsBuilder{}, - givenAccount: account, givenRequest: openrtb_ext.RequestWrapper{BidRequest: &req}, expectedRequest: reqUpdated, expectedErr: nil, @@ -755,7 +748,6 @@ func TestExecuteProcessedAuctionStage(t *testing.T) { { description: "Stage execution can be rejected - and later hooks rejected", givenPlanBuilder: TestRejectPlanBuilder{}, - givenAccount: nil, givenRequest: openrtb_ext.RequestWrapper{BidRequest: &req}, expectedRequest: req, expectedErr: &RejectError{0, HookID{ModuleCode: "foobar", HookImplCode: "foo"}, hooks.StageProcessedAuctionRequest.String()}, @@ -788,7 +780,6 @@ func TestExecuteProcessedAuctionStage(t *testing.T) { { description: "Request can be changed when a hook times out", givenPlanBuilder: TestWithTimeoutPlanBuilder{}, - givenAccount: account, givenRequest: openrtb_ext.RequestWrapper{BidRequest: &req}, expectedRequest: reqUpdated, expectedErr: nil, @@ -836,7 +827,6 @@ func TestExecuteProcessedAuctionStage(t *testing.T) { { description: "Modules contexts are preserved and correct", givenPlanBuilder: TestWithModuleContextsPlanBuilder{}, - givenAccount: account, givenRequest: openrtb_ext.RequestWrapper{BidRequest: &req}, expectedRequest: req, expectedErr: nil, @@ -896,7 +886,10 @@ func TestExecuteProcessedAuctionStage(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(ti *testing.T) { exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) - exec.SetAccount(test.givenAccount) + + privacyConfig := getModuleActivities("foo", false, false) + ac := privacy.NewActivityControl(privacyConfig) + exec.SetActivityControl(ac) err := exec.ExecuteProcessedAuctionStage(&test.givenRequest) @@ -917,7 +910,6 @@ func TestExecuteProcessedAuctionStage(t *testing.T) { func TestExecuteBidderRequestStage(t *testing.T) { bidderName := "the-bidder" foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} - account := &config.Account{} expectedBidderRequest := &openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id"}} expectedUpdatedBidderRequest := &openrtb2.BidRequest{ @@ -933,17 +925,16 @@ func TestExecuteBidderRequestStage(t *testing.T) { description string givenBidderRequest *openrtb2.BidRequest givenPlanBuilder hooks.ExecutionPlanBuilder - givenAccount *config.Account expectedBidderRequest *openrtb2.BidRequest expectedReject *RejectError expectedModuleContexts *moduleContexts expectedStageOutcomes []StageOutcome + privacyConfig *config.AccountPrivacy }{ { description: "Payload not changed if hook execution plan empty", givenBidderRequest: &openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id"}}, givenPlanBuilder: hooks.EmptyPlanBuilder{}, - givenAccount: account, expectedBidderRequest: expectedBidderRequest, expectedReject: nil, expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, @@ -953,7 +944,6 @@ func TestExecuteBidderRequestStage(t *testing.T) { description: "Payload changed if hooks return mutations", givenBidderRequest: &openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id"}}, givenPlanBuilder: TestApplyHookMutationsBuilder{}, - givenAccount: account, expectedBidderRequest: expectedUpdatedBidderRequest, expectedReject: nil, expectedModuleContexts: foobarModuleCtx, @@ -1011,7 +1001,6 @@ func TestExecuteBidderRequestStage(t *testing.T) { description: "Stage execution can be rejected - and later hooks rejected", givenBidderRequest: &openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id"}}, givenPlanBuilder: TestRejectPlanBuilder{}, - givenAccount: nil, expectedBidderRequest: expectedBidderRequest, expectedReject: &RejectError{0, HookID{ModuleCode: "foobar", HookImplCode: "foo"}, hooks.StageBidderRequest.String()}, expectedModuleContexts: foobarModuleCtx, @@ -1063,7 +1052,6 @@ func TestExecuteBidderRequestStage(t *testing.T) { description: "Stage execution can be timed out", givenBidderRequest: &openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id"}}, givenPlanBuilder: TestWithTimeoutPlanBuilder{}, - givenAccount: account, expectedBidderRequest: expectedUpdatedBidderRequest, expectedReject: nil, expectedModuleContexts: foobarModuleCtx, @@ -1115,7 +1103,6 @@ func TestExecuteBidderRequestStage(t *testing.T) { description: "Modules contexts are preserved and correct", givenBidderRequest: &openrtb2.BidRequest{ID: "some-id", User: &openrtb2.User{ID: "user-id"}}, givenPlanBuilder: TestWithModuleContextsPlanBuilder{}, - givenAccount: account, expectedBidderRequest: expectedBidderRequest, expectedReject: nil, expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ @@ -1169,9 +1156,11 @@ func TestExecuteBidderRequestStage(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(t *testing.T) { exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) - exec.SetAccount(test.givenAccount) + privacyConfig := getModuleActivities("foo", false, false) + ac := privacy.NewActivityControl(privacyConfig) + exec.SetActivityControl(ac) - reject := exec.ExecuteBidderRequestStage(test.givenBidderRequest, bidderName) + reject := exec.ExecuteBidderRequestStage(&openrtb_ext.RequestWrapper{BidRequest: test.givenBidderRequest}, bidderName) assert.Equal(t, test.expectedReject, reject, "Unexpected stage reject.") assert.Equal(t, test.expectedBidderRequest, test.givenBidderRequest, "Incorrect bidder request.") @@ -1187,9 +1176,48 @@ func TestExecuteBidderRequestStage(t *testing.T) { } } +func getModuleActivities(componentName string, allowTransmitUserFPD, allowTransmitPreciseGeo bool) *config.AccountPrivacy { + return &config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ + TransmitUserFPD: buildDefaultActivityConfig(componentName, allowTransmitUserFPD), + TransmitPreciseGeo: buildDefaultActivityConfig(componentName, allowTransmitPreciseGeo), + }, + } +} + +func getTransmitUFPDActivityConfig(componentName string, allow bool) *config.AccountPrivacy { + return &config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ + TransmitUserFPD: buildDefaultActivityConfig(componentName, allow), + }, + } +} + +func getTransmitPreciseGeoActivityConfig(componentName string, allow bool) *config.AccountPrivacy { + return &config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ + TransmitPreciseGeo: buildDefaultActivityConfig(componentName, allow), + }, + } +} + +func buildDefaultActivityConfig(componentName string, allow bool) config.Activity { + return config.Activity{ + Default: ptrutil.ToPtr(true), + Rules: []config.ActivityRule{ + { + Allow: allow, + Condition: config.ActivityCondition{ + ComponentName: []string{componentName}, + ComponentType: []string{"general"}, + }, + }, + }, + } +} + func TestExecuteRawBidderResponseStage(t *testing.T) { foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} - account := &config.Account{} resp := adapters.BidderResponse{Bids: []*adapters.TypedBid{{DealPriority: 1}}} expResp := adapters.BidderResponse{Bids: []*adapters.TypedBid{{DealPriority: 10}}} vEntity := entity("the-bidder") @@ -1197,7 +1225,6 @@ func TestExecuteRawBidderResponseStage(t *testing.T) { testCases := []struct { description string givenPlanBuilder hooks.ExecutionPlanBuilder - givenAccount *config.Account givenBidderResponse adapters.BidderResponse expectedBidderResponse adapters.BidderResponse expectedReject *RejectError @@ -1207,7 +1234,6 @@ func TestExecuteRawBidderResponseStage(t *testing.T) { { description: "Payload not changed if hook execution plan empty", givenPlanBuilder: hooks.EmptyPlanBuilder{}, - givenAccount: account, givenBidderResponse: resp, expectedBidderResponse: resp, expectedReject: nil, @@ -1217,7 +1243,6 @@ func TestExecuteRawBidderResponseStage(t *testing.T) { { description: "Payload changed if hooks return mutations", givenPlanBuilder: TestApplyHookMutationsBuilder{}, - givenAccount: account, givenBidderResponse: resp, expectedBidderResponse: expResp, expectedReject: nil, @@ -1250,7 +1275,6 @@ func TestExecuteRawBidderResponseStage(t *testing.T) { { description: "Stage execution can be rejected", givenPlanBuilder: TestRejectPlanBuilder{}, - givenAccount: nil, givenBidderResponse: resp, expectedBidderResponse: resp, expectedReject: &RejectError{0, HookID{ModuleCode: "foobar", HookImplCode: "foo"}, hooks.StageRawBidderResponse.String()}, @@ -1283,7 +1307,6 @@ func TestExecuteRawBidderResponseStage(t *testing.T) { { description: "Response can be changed when a hook times out", givenPlanBuilder: TestWithTimeoutPlanBuilder{}, - givenAccount: account, givenBidderResponse: resp, expectedBidderResponse: expResp, expectedReject: nil, @@ -1330,7 +1353,6 @@ func TestExecuteRawBidderResponseStage(t *testing.T) { { description: "Modules contexts are preserved and correct", givenPlanBuilder: TestWithModuleContextsPlanBuilder{}, - givenAccount: account, givenBidderResponse: resp, expectedBidderResponse: expResp, expectedReject: nil, @@ -1390,7 +1412,10 @@ func TestExecuteRawBidderResponseStage(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(ti *testing.T) { exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) - exec.SetAccount(test.givenAccount) + + privacyConfig := getModuleActivities("foo", false, false) + ac := privacy.NewActivityControl(privacyConfig) + exec.SetActivityControl(ac) reject := exec.ExecuteRawBidderResponseStage(&test.givenBidderResponse, "the-bidder") @@ -1410,7 +1435,6 @@ func TestExecuteRawBidderResponseStage(t *testing.T) { func TestExecuteAllProcessedBidResponsesStage(t *testing.T) { foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} - account := &config.Account{} expectedAllProcBidResponses := map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{ "some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 1}}}, @@ -1423,7 +1447,6 @@ func TestExecuteAllProcessedBidResponsesStage(t *testing.T) { description string givenBiddersResponse map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid givenPlanBuilder hooks.ExecutionPlanBuilder - givenAccount *config.Account expectedBiddersResponse map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid expectedReject *RejectError expectedModuleContexts *moduleContexts @@ -1435,7 +1458,6 @@ func TestExecuteAllProcessedBidResponsesStage(t *testing.T) { "some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 1}}}, }, givenPlanBuilder: hooks.EmptyPlanBuilder{}, - givenAccount: account, expectedBiddersResponse: expectedAllProcBidResponses, expectedReject: nil, expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{}}, @@ -1447,7 +1469,6 @@ func TestExecuteAllProcessedBidResponsesStage(t *testing.T) { "some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 1}}}, }, givenPlanBuilder: TestApplyHookMutationsBuilder{}, - givenAccount: account, expectedBiddersResponse: expectedUpdatedAllProcBidResponses, expectedReject: nil, expectedModuleContexts: foobarModuleCtx, @@ -1506,7 +1527,6 @@ func TestExecuteAllProcessedBidResponsesStage(t *testing.T) { "some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 1}}}, }, givenPlanBuilder: TestRejectPlanBuilder{}, - givenAccount: nil, expectedBiddersResponse: expectedUpdatedAllProcBidResponses, expectedReject: &RejectError{0, HookID{ModuleCode: "foobar", HookImplCode: "foo"}, hooks.StageAllProcessedBidResponses.String()}, expectedModuleContexts: foobarModuleCtx, @@ -1571,7 +1591,6 @@ func TestExecuteAllProcessedBidResponsesStage(t *testing.T) { "some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 1}}}, }, givenPlanBuilder: TestWithTimeoutPlanBuilder{}, - givenAccount: account, expectedBiddersResponse: expectedUpdatedAllProcBidResponses, expectedReject: nil, expectedModuleContexts: foobarModuleCtx, @@ -1620,7 +1639,6 @@ func TestExecuteAllProcessedBidResponsesStage(t *testing.T) { "some-bidder": {Bids: []*entities.PbsOrtbBid{{DealPriority: 1}}}, }, givenPlanBuilder: TestWithModuleContextsPlanBuilder{}, - givenAccount: account, expectedBiddersResponse: expectedAllProcBidResponses, expectedReject: nil, expectedModuleContexts: &moduleContexts{ctxs: map[string]hookstage.ModuleContext{ @@ -1669,7 +1687,10 @@ func TestExecuteAllProcessedBidResponsesStage(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(t *testing.T) { exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) - exec.SetAccount(test.givenAccount) + + privacyConfig := getModuleActivities("foo", false, false) + ac := privacy.NewActivityControl(privacyConfig) + exec.SetActivityControl(ac) exec.ExecuteAllProcessedBidResponsesStage(test.givenBiddersResponse) @@ -1688,14 +1709,12 @@ func TestExecuteAllProcessedBidResponsesStage(t *testing.T) { func TestExecuteAuctionResponseStage(t *testing.T) { foobarModuleCtx := &moduleContexts{ctxs: map[string]hookstage.ModuleContext{"foobar": nil}} - account := &config.Account{} resp := &openrtb2.BidResponse{CustomData: "some-custom-data"} expResp := &openrtb2.BidResponse{CustomData: "new-custom-data"} testCases := []struct { description string givenPlanBuilder hooks.ExecutionPlanBuilder - givenAccount *config.Account givenResponse *openrtb2.BidResponse expectedResponse *openrtb2.BidResponse expectedReject *RejectError @@ -1705,7 +1724,6 @@ func TestExecuteAuctionResponseStage(t *testing.T) { { description: "Payload not changed if hook execution plan empty", givenPlanBuilder: hooks.EmptyPlanBuilder{}, - givenAccount: account, givenResponse: resp, expectedResponse: resp, expectedReject: nil, @@ -1715,7 +1733,6 @@ func TestExecuteAuctionResponseStage(t *testing.T) { { description: "Payload changed if hooks return mutations", givenPlanBuilder: TestApplyHookMutationsBuilder{}, - givenAccount: account, givenResponse: resp, expectedResponse: expResp, expectedReject: nil, @@ -1748,7 +1765,6 @@ func TestExecuteAuctionResponseStage(t *testing.T) { { description: "Stage execution can't be rejected - stage doesn't support rejection", givenPlanBuilder: TestRejectPlanBuilder{}, - givenAccount: nil, givenResponse: resp, expectedResponse: expResp, expectedReject: &RejectError{0, HookID{ModuleCode: "foobar", HookImplCode: "foo"}, hooks.StageAuctionResponse.String()}, @@ -1811,7 +1827,6 @@ func TestExecuteAuctionResponseStage(t *testing.T) { { description: "Request can be changed when a hook times out", givenPlanBuilder: TestWithTimeoutPlanBuilder{}, - givenAccount: account, givenResponse: resp, expectedResponse: expResp, expectedReject: nil, @@ -1858,7 +1873,6 @@ func TestExecuteAuctionResponseStage(t *testing.T) { { description: "Modules contexts are preserved and correct", givenPlanBuilder: TestWithModuleContextsPlanBuilder{}, - givenAccount: account, givenResponse: resp, expectedResponse: resp, expectedReject: nil, @@ -1918,7 +1932,10 @@ func TestExecuteAuctionResponseStage(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(t *testing.T) { exec := NewHookExecutor(test.givenPlanBuilder, EndpointAuction, &metricsConfig.NilMetricsEngine{}) - exec.SetAccount(test.givenAccount) + + privacyConfig := getModuleActivities("foo", false, false) + ac := privacy.NewActivityControl(privacyConfig) + exec.SetActivityControl(ac) exec.ExecuteAuctionResponseStage(test.givenResponse) diff --git a/hooks/hookexecution/mocks_test.go b/hooks/hookexecution/mocks_test.go index ff543c38a07..1a415adba84 100644 --- a/hooks/hookexecution/mocks_test.go +++ b/hooks/hookexecution/mocks_test.go @@ -5,8 +5,9 @@ import ( "errors" "time" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/hooks/hookstage" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/ptrutil" ) type mockUpdateHeaderEntrypointHook struct{} @@ -57,14 +58,12 @@ func (e mockUpdateBodyHook) HandleEntrypointHook(_ context.Context, _ hookstage. func (e mockUpdateBodyHook) HandleRawAuctionHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.RawAuctionRequestPayload) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { c := hookstage.ChangeSet[hookstage.RawAuctionRequestPayload]{} c.AddMutation( - func(payload hookstage.RawAuctionRequestPayload) (hookstage.RawAuctionRequestPayload, error) { - payload = []byte(`{"name": "John", "last_name": "Doe", "foo": "bar"}`) - return payload, nil + func(_ hookstage.RawAuctionRequestPayload) (hookstage.RawAuctionRequestPayload, error) { + return []byte(`{"name": "John", "last_name": "Doe", "foo": "bar"}`), nil }, hookstage.MutationUpdate, "body", "foo", ).AddMutation( - func(payload hookstage.RawAuctionRequestPayload) (hookstage.RawAuctionRequestPayload, error) { - payload = []byte(`{"last_name": "Doe", "foo": "bar"}`) - return payload, nil + func(_ hookstage.RawAuctionRequestPayload) (hookstage.RawAuctionRequestPayload, error) { + return []byte(`{"last_name": "Doe", "foo": "bar"}`), nil }, hookstage.MutationDelete, "body", "name", ) @@ -119,9 +118,8 @@ func (e mockTimeoutHook) HandleEntrypointHook(_ context.Context, _ hookstage.Mod func (e mockTimeoutHook) HandleRawAuctionHook(_ context.Context, _ hookstage.ModuleInvocationContext, _ hookstage.RawAuctionRequestPayload) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { time.Sleep(20 * time.Millisecond) c := hookstage.ChangeSet[hookstage.RawAuctionRequestPayload]{} - c.AddMutation(func(payload hookstage.RawAuctionRequestPayload) (hookstage.RawAuctionRequestPayload, error) { - payload = []byte(`{"last_name": "Doe", "foo": "bar", "address": "A st."}`) - return payload, nil + c.AddMutation(func(_ hookstage.RawAuctionRequestPayload) (hookstage.RawAuctionRequestPayload, error) { + return []byte(`{"last_name": "Doe", "foo": "bar", "address": "A st."}`), nil }, hookstage.MutationUpdate, "param", "address") return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{ChangeSet: c}, nil @@ -131,7 +129,7 @@ func (e mockTimeoutHook) HandleProcessedAuctionHook(_ context.Context, _ hooksta time.Sleep(20 * time.Millisecond) c := hookstage.ChangeSet[hookstage.ProcessedAuctionRequestPayload]{} c.AddMutation(func(payload hookstage.ProcessedAuctionRequestPayload) (hookstage.ProcessedAuctionRequestPayload, error) { - payload.RequestWrapper.User.CustomData = "some-custom-data" + payload.Request.User.CustomData = "some-custom-data" return payload, nil }, hookstage.MutationUpdate, "bidRequest", "user.customData") @@ -142,7 +140,7 @@ func (e mockTimeoutHook) HandleBidderRequestHook(_ context.Context, _ hookstage. time.Sleep(20 * time.Millisecond) c := hookstage.ChangeSet[hookstage.BidderRequestPayload]{} c.AddMutation(func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) { - payload.BidRequest.User.CustomData = "some-custom-data" + payload.Request.User.CustomData = "some-custom-data" return payload, nil }, hookstage.MutationUpdate, "bidRequest", "user.customData") @@ -153,7 +151,7 @@ func (e mockTimeoutHook) HandleRawBidderResponseHook(_ context.Context, _ hookst time.Sleep(20 * time.Millisecond) c := hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} c.AddMutation(func(payload hookstage.RawBidderResponsePayload) (hookstage.RawBidderResponsePayload, error) { - payload.Bids[0].BidMeta = &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "new-code"} + payload.BidderResponse.Bids[0].BidMeta = &openrtb_ext.ExtBidPrebidMeta{AdapterCode: "new-code"} return payload, nil }, hookstage.MutationUpdate, "bidderResponse", "bidMeta.AdapterCode") @@ -305,8 +303,8 @@ func (e mockUpdateBidRequestHook) HandleProcessedAuctionHook(_ context.Context, c := hookstage.ChangeSet[hookstage.ProcessedAuctionRequestPayload]{} c.AddMutation( func(payload hookstage.ProcessedAuctionRequestPayload) (hookstage.ProcessedAuctionRequestPayload, error) { - payload.RequestWrapper.User.Yob = 2000 - userExt, err := payload.RequestWrapper.GetUserExt() + payload.Request.User.Yob = 2000 + userExt, err := payload.Request.GetUserExt() if err != nil { return payload, err } @@ -318,7 +316,7 @@ func (e mockUpdateBidRequestHook) HandleProcessedAuctionHook(_ context.Context, }, hookstage.MutationUpdate, "bidRequest", "user.yob", ).AddMutation( func(payload hookstage.ProcessedAuctionRequestPayload) (hookstage.ProcessedAuctionRequestPayload, error) { - payload.RequestWrapper.User.Consent = "true" + payload.Request.User.Consent = "true" return payload, nil }, hookstage.MutationUpdate, "bidRequest", "user.consent", ) @@ -330,12 +328,16 @@ func (e mockUpdateBidRequestHook) HandleBidderRequestHook(_ context.Context, _ h c := hookstage.ChangeSet[hookstage.BidderRequestPayload]{} c.AddMutation( func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) { - payload.BidRequest.User.Yob = 2000 + user := ptrutil.Clone(payload.Request.User) + user.Yob = 2000 + payload.Request.User = user return payload, nil }, hookstage.MutationUpdate, "bidRequest", "user.yob", ).AddMutation( func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) { - payload.BidRequest.User.Consent = "true" + user := ptrutil.Clone(payload.Request.User) + user.Consent = "true" + payload.Request.User = user return payload, nil }, hookstage.MutationUpdate, "bidRequest", "user.consent", ) @@ -349,7 +351,7 @@ func (e mockUpdateBidderResponseHook) HandleRawBidderResponseHook(_ context.Cont c := hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} c.AddMutation( func(payload hookstage.RawBidderResponsePayload) (hookstage.RawBidderResponsePayload, error) { - payload.Bids[0].DealPriority = 10 + payload.BidderResponse.Bids[0].DealPriority = 10 return payload, nil }, hookstage.MutationUpdate, "bidderResponse", "bid.deal-priority", ) diff --git a/hooks/hookexecution/outcome.go b/hooks/hookexecution/outcome.go index 3eeb7bcef5e..d17d3cd4672 100644 --- a/hooks/hookexecution/outcome.go +++ b/hooks/hookexecution/outcome.go @@ -3,7 +3,7 @@ package hookexecution import ( "time" - "github.com/prebid/prebid-server/hooks/hookanalytics" + "github.com/prebid/prebid-server/v3/hooks/hookanalytics" ) // Status indicates the result of hook execution. diff --git a/hooks/hookexecution/test_utils.go b/hooks/hookexecution/test_utils.go index 2a604d851f5..b26d4aae4bd 100644 --- a/hooks/hookexecution/test_utils.go +++ b/hooks/hookexecution/test_utils.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/hooks/hookstage/allprocessedbidresponses.go b/hooks/hookstage/allprocessedbidresponses.go index 3f90c5624ee..6bfe0d80821 100644 --- a/hooks/hookstage/allprocessedbidresponses.go +++ b/hooks/hookstage/allprocessedbidresponses.go @@ -3,8 +3,8 @@ package hookstage import ( "context" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // AllProcessedBidResponses hooks are invoked over a list of all diff --git a/hooks/hookstage/auctionresponse.go b/hooks/hookstage/auctionresponse.go index d47c78de5e1..c0810b196c1 100644 --- a/hooks/hookstage/auctionresponse.go +++ b/hooks/hookstage/auctionresponse.go @@ -3,7 +3,7 @@ package hookstage import ( "context" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" ) // AuctionResponse hooks are invoked at the very end of request processing. diff --git a/hooks/hookstage/bidderrequest.go b/hooks/hookstage/bidderrequest.go index 6609a103279..8c27f07242a 100644 --- a/hooks/hookstage/bidderrequest.go +++ b/hooks/hookstage/bidderrequest.go @@ -3,7 +3,7 @@ package hookstage import ( "context" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // BidderRequest hooks are invoked for each bidder participating in auction. @@ -25,6 +25,20 @@ type BidderRequest interface { // distilled for the particular bidder. // Hooks are allowed to modify openrtb2.BidRequest using mutations. type BidderRequestPayload struct { - BidRequest *openrtb2.BidRequest - Bidder string + Request *openrtb_ext.RequestWrapper + Bidder string +} + +func (brp *BidderRequestPayload) GetBidderRequestPayload() *openrtb_ext.RequestWrapper { + return brp.Request +} + +func (brp *BidderRequestPayload) SetBidderRequestPayload(br *openrtb_ext.RequestWrapper) { + brp.Request = br +} + +// RequestUpdater allows reading and writing a bid request +type RequestUpdater interface { + GetBidderRequestPayload() *openrtb_ext.RequestWrapper + SetBidderRequestPayload(br *openrtb_ext.RequestWrapper) } diff --git a/hooks/hookstage/bidderrequest_mutations.go b/hooks/hookstage/bidderrequest_mutations.go index 6dfd1c6438f..3ab571ff8e7 100644 --- a/hooks/hookstage/bidderrequest_mutations.go +++ b/hooks/hookstage/bidderrequest_mutations.go @@ -3,8 +3,9 @@ package hookstage import ( "errors" - "github.com/prebid/openrtb/v19/adcom1" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" + + "github.com/prebid/openrtb/v20/adcom1" ) func (c *ChangeSet[T]) BidderRequest() ChangeSetBidderRequest[T] { @@ -31,12 +32,12 @@ func (c ChangeSetBidderRequest[T]) BApp() ChangeSetBApp[T] { return ChangeSetBApp[T]{changeSetBidderRequest: c} } -func (c ChangeSetBidderRequest[T]) castPayload(p T) (*openrtb2.BidRequest, error) { +func (c ChangeSetBidderRequest[T]) castPayload(p T) (*openrtb_ext.RequestWrapper, error) { if payload, ok := any(p).(BidderRequestPayload); ok { - if payload.BidRequest == nil { - return nil, errors.New("empty BidRequest provided") + if payload.Request == nil || payload.Request.BidRequest == nil { + return nil, errors.New("payload contains a nil bid request") } - return payload.BidRequest, nil + return payload.Request, nil } return nil, errors.New("failed to cast BidderRequestPayload") } diff --git a/hooks/hookstage/invocation.go b/hooks/hookstage/invocation.go index 7f465382b20..6408e8667df 100644 --- a/hooks/hookstage/invocation.go +++ b/hooks/hookstage/invocation.go @@ -3,7 +3,7 @@ package hookstage import ( "encoding/json" - "github.com/prebid/prebid-server/hooks/hookanalytics" + "github.com/prebid/prebid-server/v3/hooks/hookanalytics" ) // HookResult represents the result of execution the concrete hook instance. diff --git a/hooks/hookstage/processedauctionrequest.go b/hooks/hookstage/processedauctionrequest.go index fe06bc6fdbd..06742138826 100644 --- a/hooks/hookstage/processedauctionrequest.go +++ b/hooks/hookstage/processedauctionrequest.go @@ -3,7 +3,7 @@ package hookstage import ( "context" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // ProcessedAuctionRequest hooks are invoked after the request is parsed @@ -26,5 +26,13 @@ type ProcessedAuctionRequest interface { // ProcessedAuctionRequestPayload consists of the openrtb_ext.RequestWrapper object. // Hooks are allowed to modify openrtb_ext.RequestWrapper using mutations. type ProcessedAuctionRequestPayload struct { - RequestWrapper *openrtb_ext.RequestWrapper + Request *openrtb_ext.RequestWrapper +} + +func (parp *ProcessedAuctionRequestPayload) GetBidderRequestPayload() *openrtb_ext.RequestWrapper { + return parp.Request +} + +func (parp *ProcessedAuctionRequestPayload) SetBidderRequestPayload(br *openrtb_ext.RequestWrapper) { + parp.Request = br } diff --git a/hooks/hookstage/rawbidderresponse.go b/hooks/hookstage/rawbidderresponse.go index d450d6d0681..52d1d8a1ba1 100644 --- a/hooks/hookstage/rawbidderresponse.go +++ b/hooks/hookstage/rawbidderresponse.go @@ -3,7 +3,7 @@ package hookstage import ( "context" - "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/v3/adapters" ) // RawBidderResponse hooks are invoked for each bidder participating in auction. @@ -21,10 +21,9 @@ type RawBidderResponse interface { ) (HookResult[RawBidderResponsePayload], error) } -// RawBidderResponsePayload consists of a list of adapters.TypedBid -// objects representing bids returned by a particular bidder. -// Hooks are allowed to modify bids using mutations. +// RawBidderResponsePayload consists of a bidder response returned by a particular bidder. +// Hooks are allowed to modify bidder response using mutations. type RawBidderResponsePayload struct { - Bids []*adapters.TypedBid - Bidder string + BidderResponse *adapters.BidderResponse + Bidder string } diff --git a/hooks/hookstage/rawbidderresponse_mutations.go b/hooks/hookstage/rawbidderresponse_mutations.go index 61c0de10bde..027598c6518 100644 --- a/hooks/hookstage/rawbidderresponse_mutations.go +++ b/hooks/hookstage/rawbidderresponse_mutations.go @@ -3,7 +3,7 @@ package hookstage import ( "errors" - "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/v3/adapters" ) func (c *ChangeSet[T]) RawBidderResponse() ChangeSetRawBidderResponse[T] { @@ -29,11 +29,12 @@ type ChangeSetBids[T any] struct { changeSetRawBidderResponse ChangeSetRawBidderResponse[T] } -func (c ChangeSetBids[T]) Update(bids []*adapters.TypedBid) { +// UpdateBids updates the list of bids present in bidder-response using mutations. +func (c ChangeSetBids[T]) UpdateBids(bids []*adapters.TypedBid) { c.changeSetRawBidderResponse.changeSet.AddMutation(func(p T) (T, error) { bidderPayload, err := c.changeSetRawBidderResponse.castPayload(p) if err == nil { - bidderPayload.Bids = bids + bidderPayload.BidderResponse.Bids = bids } if payload, ok := any(bidderPayload).(T); ok { return payload, nil diff --git a/hooks/plan.go b/hooks/plan.go index c6fda959762..62655f6cf5c 100644 --- a/hooks/plan.go +++ b/hooks/plan.go @@ -4,8 +4,8 @@ import ( "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/hooks/hookstage" ) type Stage string diff --git a/hooks/plan_test.go b/hooks/plan_test.go index 8af49b42e17..fd2d0554e5c 100644 --- a/hooks/plan_test.go +++ b/hooks/plan_test.go @@ -5,9 +5,9 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/hooks/hookstage" + "github.com/prebid/prebid-server/v3/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/hooks/repo.go b/hooks/repo.go index 40276701b34..cb77c7bbea6 100644 --- a/hooks/repo.go +++ b/hooks/repo.go @@ -3,7 +3,7 @@ package hooks import ( "fmt" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v3/hooks/hookstage" ) // HookRepository is the interface that exposes methods diff --git a/hooks/repo_test.go b/hooks/repo_test.go index ae523c98773..d6d37133511 100644 --- a/hooks/repo_test.go +++ b/hooks/repo_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v3/hooks/hookstage" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/injector/injector.go b/injector/injector.go new file mode 100644 index 00000000000..9de2c1b726b --- /dev/null +++ b/injector/injector.go @@ -0,0 +1,401 @@ +package injector + +import ( + "encoding/xml" + "fmt" + "io" + "strings" + + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/metrics" +) + +const ( + emptyAdmResponse = `prebid.org wrapper` +) + +const ( + companionStartTag = "" + companionEndTag = "" + nonLinearStartTag = "" + nonLinearEndTag = "" + videoClickStartTag = "" + videoClickEndTag = "" + trackingEventStartTag = "" + trackingEventEndTag = "" + clickTrackingStartTag = "" + impressionStartTag = "" + errorStartTag = "" + nonLinearClickTrackingStartTag = "" + companionClickThroughStartTag = "" + tracking = "tracking" + companionclickthrough = "companionclickthrough" + nonlinearclicktracking = "nonlinearclicktracking" + impression = "impression" + err = "error" + clicktracking = "clicktracking" + adId = "adid" + trackingStartTag = `" +) + +const ( + inlineCase = "InLine" + wrapperCase = "Wrapper" + creativeCase = "Creative" + linearCase = "Linear" + nonLinearCase = "NonLinear" + videoClicksCase = "VideoClicks" + nonLinearAdsCase = "NonLinearAds" + trackingEventsCase = "TrackingEvents" + impressionCase = "Impression" + errorCase = "Error" + companionCase = "Companion" + companionAdsCase = "CompanionAds" +) + +type Injector interface { + InjectTracker(vastXML string, NURL string) string +} + +type VASTEvents struct { + Errors []string + Impressions []string + VideoClicks []string + NonLinearClickTracking []string + CompanionClickThrough []string + TrackingEvents map[string][]string +} + +type InjectionState struct { + injectTracker bool + injectVideoClicks bool + inlineWrapperTagFound bool + wrapperTagFound bool + impressionTagFound bool + errorTagFound bool + creativeId string + isCreative bool + companionTagFound bool + nonLinearTagFound bool +} + +type TrackerInjector struct { + replacer macros.Replacer + events VASTEvents + me metrics.MetricsEngine + provider *macros.MacroProvider +} + +var trimRunes = "\t\r\b\n " + +func NewTrackerInjector(replacer macros.Replacer, provider *macros.MacroProvider, events VASTEvents) *TrackerInjector { + return &TrackerInjector{ + replacer: replacer, + provider: provider, + events: events, + } +} + +func (trackerinjector *TrackerInjector) InjectTracker(vastXML string, NURL string) (string, error) { + if vastXML == "" && NURL == "" { + // TODO Log a adapter..requests.badserverresponse + return vastXML, fmt.Errorf("invalid Vast XML") + } + + if vastXML == "" { + return fmt.Sprintf(emptyAdmResponse, NURL), nil + } + + var outputXML strings.Builder + encoder := xml.NewEncoder(&outputXML) + state := &InjectionState{ + injectTracker: false, + injectVideoClicks: false, + inlineWrapperTagFound: false, + wrapperTagFound: false, + impressionTagFound: false, + errorTagFound: false, + creativeId: "", + isCreative: false, + companionTagFound: false, + nonLinearTagFound: false, + } + + reader := strings.NewReader(vastXML) + decoder := xml.NewDecoder(reader) + + for { + rawToken, err := decoder.RawToken() + if err != nil { + if err == io.EOF { + break + } else { + return "", fmt.Errorf("XML processing error: %w", err) + } + } + + switch token := rawToken.(type) { + case xml.StartElement: + err = trackerinjector.handleStartElement(token, state, &outputXML, encoder) + case xml.EndElement: + err = trackerinjector.handleEndElement(token, state, &outputXML, encoder) + case xml.CharData: + charData := strings.Trim(string(token), trimRunes) + if len(charData) != 0 { + err = encoder.Flush() + outputXML.WriteString("") + } + default: + err = encoder.EncodeToken(rawToken) + } + + if err != nil { + return "", fmt.Errorf("XML processing error: %w", err) + } + } + + if err := encoder.Flush(); err != nil { + return "", fmt.Errorf("XML processing error: %w", err) + } + + if !state.inlineWrapperTagFound { + // Todo log adapter..requests.badserverresponse metrics + return vastXML, fmt.Errorf("invalid VastXML, inline/wrapper tag not found") + } + return outputXML.String(), nil +} + +func (trackerinjector *TrackerInjector) handleStartElement(token xml.StartElement, state *InjectionState, outputXML *strings.Builder, encoder *xml.Encoder) error { + var err error + switch token.Name.Local { + case wrapperCase: + state.wrapperTagFound = true + if err = encoder.EncodeToken(token); err != nil { + return err + } + case creativeCase: + state.isCreative = true + for _, attr := range token.Attr { + if strings.ToLower(attr.Name.Local) == adId { + state.creativeId = attr.Value + } + } + if err = encoder.EncodeToken(token); err != nil { + return err + } + case linearCase: + state.injectVideoClicks = true + state.injectTracker = true + if err = encoder.EncodeToken(token); err != nil { + return err + } + case videoClicksCase: + state.injectVideoClicks = false + if err = encoder.EncodeToken(token); err != nil { + return err + } + if err = encoder.Flush(); err != nil { + return err + } + trackerinjector.addClickTrackingEvent(outputXML, state.creativeId, false) + case nonLinearAdsCase: + state.injectTracker = true + if err = encoder.EncodeToken(token); err != nil { + return err + } + case trackingEventsCase: + if state.isCreative { + state.injectTracker = false + if err = encoder.EncodeToken(token); err != nil { + return err + } + if err = encoder.Flush(); err != nil { + return err + } + trackerinjector.addTrackingEvent(outputXML, state.creativeId, false) + } + default: + if err = encoder.EncodeToken(token); err != nil { + return err + } + } + return nil +} + +func (trackerinjector *TrackerInjector) handleEndElement(token xml.EndElement, state *InjectionState, outputXML *strings.Builder, encoder *xml.Encoder) error { + var err error + switch token.Name.Local { + case impressionCase: + if err = encoder.EncodeToken(token); err != nil { + return err + } + if err = encoder.Flush(); err != nil { + return err + } + if !state.impressionTagFound { + trackerinjector.addImpressionTrackingEvent(outputXML) + state.impressionTagFound = true + } + case errorCase: + if err = encoder.EncodeToken(token); err != nil { + return err + } + if err = encoder.Flush(); err != nil { + return err + } + if !state.errorTagFound { + trackerinjector.addErrorTrackingEvent(outputXML) + state.errorTagFound = true + } + case nonLinearAdsCase: + if state.injectTracker { + state.injectTracker = false + if err = encoder.Flush(); err != nil { + return err + } + trackerinjector.addTrackingEvent(outputXML, state.creativeId, true) + if !state.nonLinearTagFound && state.wrapperTagFound { + trackerinjector.addNonLinearClickTrackingEvent(outputXML, state.creativeId, true) + } + if err = encoder.EncodeToken(token); err != nil { + return err + } + } + case linearCase: + if state.injectVideoClicks { + state.injectVideoClicks = false + if err = encoder.Flush(); err != nil { + return err + } + trackerinjector.addClickTrackingEvent(outputXML, state.creativeId, true) + } + if state.injectTracker { + state.injectTracker = false + if err = encoder.Flush(); err != nil { + return err + } + trackerinjector.addTrackingEvent(outputXML, state.creativeId, true) + } + encoder.EncodeToken(token) + case inlineCase, wrapperCase: + state.wrapperTagFound = false + state.inlineWrapperTagFound = true + if err = encoder.Flush(); err != nil { + return err + } + if !state.impressionTagFound { + trackerinjector.addImpressionTrackingEvent(outputXML) + } + state.impressionTagFound = false + if !state.errorTagFound { + trackerinjector.addErrorTrackingEvent(outputXML) + } + state.errorTagFound = false + if err = encoder.EncodeToken(token); err != nil { + return err + } + case nonLinearCase: + if err = encoder.Flush(); err != nil { + return err + } + trackerinjector.addNonLinearClickTrackingEvent(outputXML, state.creativeId, false) + state.nonLinearTagFound = true + if err = encoder.EncodeToken(token); err != nil { + return err + } + case companionCase: + state.companionTagFound = true + if err = encoder.Flush(); err != nil { + return err + } + trackerinjector.addCompanionClickThroughEvent(outputXML, state.creativeId, false) + if err = encoder.EncodeToken(token); err != nil { + return err + } + case creativeCase: + state.isCreative = false + if err = encoder.EncodeToken(token); err != nil { + return err + } + case companionAdsCase: + if !state.companionTagFound && state.wrapperTagFound { + if err = encoder.Flush(); err != nil { + return err + } + trackerinjector.addCompanionClickThroughEvent(outputXML, state.creativeId, true) + } + if err = encoder.EncodeToken(token); err != nil { + return err + } + default: + if err = encoder.EncodeToken(token); err != nil { + return err + } + } + return nil +} + +func (trackerinjector *TrackerInjector) addTrackingEvent(outputXML *strings.Builder, creativeId string, addParentTag bool) { + if addParentTag { + outputXML.WriteString(trackingEventStartTag) + } + for typ, urls := range trackerinjector.events.TrackingEvents { + trackerinjector.writeTrackingEvent(urls, outputXML, fmt.Sprintf(trackingStartTag, typ), trackingEndTag, creativeId, typ, tracking) + } + if addParentTag { + outputXML.WriteString(trackingEventEndTag) + } +} + +func (trackerinjector *TrackerInjector) addClickTrackingEvent(outputXML *strings.Builder, creativeId string, addParentTag bool) { + if addParentTag { + outputXML.WriteString(videoClickStartTag) + } + trackerinjector.writeTrackingEvent(trackerinjector.events.VideoClicks, outputXML, clickTrackingStartTag, clickTrackingEndTag, creativeId, "", clicktracking) + if addParentTag { + outputXML.WriteString(videoClickEndTag) + } +} + +func (trackerinjector *TrackerInjector) addImpressionTrackingEvent(outputXML *strings.Builder) { + trackerinjector.writeTrackingEvent(trackerinjector.events.Impressions, outputXML, impressionStartTag, impressionEndTag, "", "", impression) +} + +func (trackerinjector *TrackerInjector) addErrorTrackingEvent(outputXML *strings.Builder) { + trackerinjector.writeTrackingEvent(trackerinjector.events.Errors, outputXML, errorStartTag, errorEndTag, "", "", err) +} + +func (trackerinjector *TrackerInjector) addNonLinearClickTrackingEvent(outputXML *strings.Builder, creativeId string, addParentTag bool) { + if addParentTag { + outputXML.WriteString(nonLinearStartTag) + } + trackerinjector.writeTrackingEvent(trackerinjector.events.NonLinearClickTracking, outputXML, nonLinearClickTrackingStartTag, nonLinearClickTrackingEndTag, creativeId, "", nonlinearclicktracking) + if addParentTag { + outputXML.WriteString(nonLinearEndTag) + } +} + +func (trackerinjector *TrackerInjector) addCompanionClickThroughEvent(outputXML *strings.Builder, creativeId string, addParentTag bool) { + if addParentTag { + outputXML.WriteString(companionStartTag) + } + trackerinjector.writeTrackingEvent(trackerinjector.events.CompanionClickThrough, outputXML, companionClickThroughStartTag, companionClickThroughEndTag, creativeId, "", companionclickthrough) + if addParentTag { + outputXML.WriteString(companionEndTag) + } +} + +func (trackerinjector *TrackerInjector) writeTrackingEvent(urls []string, outputXML *strings.Builder, startTag, endTag, creativeId, eventType, vastEvent string) { + trackerinjector.provider.PopulateEventMacros(creativeId, eventType, vastEvent) + for _, url := range urls { + outputXML.WriteString(startTag) + trackerinjector.replacer.Replace(outputXML, url, trackerinjector.provider) + outputXML.WriteString(endTag) + } +} diff --git a/injector/injector_test.go b/injector/injector_test.go new file mode 100644 index 00000000000..b3457b094b7 --- /dev/null +++ b/injector/injector_test.go @@ -0,0 +1,455 @@ +package injector + +import ( + "errors" + "strings" + "testing" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +var reqWrapper = &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "123", + Site: &openrtb2.Site{ + Domain: "testdomain", + Publisher: &openrtb2.Publisher{ + Domain: "publishertestdomain", + ID: "testpublisherID", + }, + Page: "pageurltest", + }, + App: &openrtb2.App{ + Domain: "testdomain", + Bundle: "testbundle", + Publisher: &openrtb2.Publisher{ + Domain: "publishertestdomain", + ID: "testpublisherID", + }, + }, + Device: &openrtb2.Device{ + Lmt: ptrutil.ToPtr(int8(1)), + }, + User: &openrtb2.User{Consent: "1", Ext: []byte(`{"consent":"2" }`)}, + Ext: []byte(`{"prebid":{"channel": {"name":"test1"},"macros":{"CUSTOMMACR1":"value1"}}}`), + }, +} + +func TestInjectTracker(t *testing.T) { + b := macros.NewProvider(reqWrapper) + b.PopulateBidMacros(&entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + ID: "bid123", + }, + }, "testSeat") + ti := NewTrackerInjector( + macros.NewStringIndexBasedReplacer(), + b, + VASTEvents{ + Errors: []string{"http://errortracker.com?macro1=##PBS-BIDID##¯o2=##PBS-APPBUNDLE##¯o3=##PBS-APPBUNDLE##¯o4=##PBS-PUBDOMAIN##¯o5=##PBS-PAGEURL##¯o6=##PBS-ACCOUNTID##¯o6=##PBS-LIMITADTRACKING##¯o7=##PBS-GDPRCONSENT##¯o8=##PBS-GDPRCONSENT##¯o9=##PBS-MACRO-CUSTOMMACR1CUST1##¯o10=##PBS-MACRO-CUSTOMMACR1CUST2##"}, + Impressions: []string{"http://impressiontracker.com?macro1=##PBS-BIDID##¯o2=##PBS-APPBUNDLE##¯o3=##PBS-APPBUNDLE##¯o4=##PBS-PUBDOMAIN##¯o5=##PBS-PAGEURL##¯o6=##PBS-ACCOUNTID##¯o6=##PBS-LIMITADTRACKING##¯o7=##PBS-GDPRCONSENT##¯o8=##PBS-GDPRCONSENT##¯o9=##PBS-MACRO-CUSTOMMACR1CUST1##¯o10=##PBS-MACRO-CUSTOMMACR1CUST2##"}, + VideoClicks: []string{"http://videoclicktracker.com?macro1=##PBS-BIDID##¯o2=##PBS-APPBUNDLE##¯o3=##PBS-APPBUNDLE##¯o4=##PBS-PUBDOMAIN##¯o5=##PBS-PAGEURL##¯o6=##PBS-ACCOUNTID##¯o6=##PBS-LIMITADTRACKING##¯o7=##PBS-GDPRCONSENT##¯o8=##PBS-GDPRCONSENT##¯o9=##PBS-MACRO-CUSTOMMACR1CUST1##¯o10=##PBS-MACRO-CUSTOMMACR1CUST2##"}, + NonLinearClickTracking: []string{"http://nonlinearclicktracker.com?macro1=##PBS-BIDID##¯o2=##PBS-APPBUNDLE##¯o3=##PBS-APPBUNDLE##¯o4=##PBS-PUBDOMAIN##¯o5=##PBS-PAGEURL##¯o6=##PBS-ACCOUNTID##¯o6=##PBS-LIMITADTRACKING##¯o7=##PBS-GDPRCONSENT##¯o8=##PBS-GDPRCONSENT##¯o9=##PBS-MACRO-CUSTOMMACR1CUST1##¯o10=##PBS-MACRO-CUSTOMMACR1CUST2##"}, + CompanionClickThrough: []string{"http://companionclicktracker.com?macro1=##PBS-BIDID##¯o2=##PBS-APPBUNDLE##¯o3=##PBS-APPBUNDLE##¯o4=##PBS-PUBDOMAIN##¯o5=##PBS-PAGEURL##¯o6=##PBS-ACCOUNTID##¯o6=##PBS-LIMITADTRACKING##¯o7=##PBS-GDPRCONSENT##¯o8=##PBS-GDPRCONSENT##¯o9=##PBS-MACRO-CUSTOMMACR1CUST1##¯o10=##PBS-MACRO-CUSTOMMACR1CUST2##"}, + TrackingEvents: map[string][]string{"firstQuartile": {"http://eventracker1.com?macro1=##PBS-BIDID##¯o2=##PBS-APPBUNDLE##¯o3=##PBS-APPBUNDLE##¯o4=##PBS-PUBDOMAIN##¯o5=##PBS-PAGEURL##¯o6=##PBS-ACCOUNTID##¯o6=##PBS-LIMITADTRACKING##¯o7=##PBS-GDPRCONSENT##¯o8=##PBS-GDPRCONSENT##¯o9=##PBS-MACRO-CUSTOMMACR1CUST1##¯o10=##PBS-MACRO-CUSTOMMACR1CUST2##"}}, + }, + ) + type args struct { + vastXML string + NURL string + } + tests := []struct { + name string + args args + want string + wantError error + }{ + { + name: "Empty vastXML and NURL present", + args: args{ + vastXML: "", + NURL: "www.nurl.com", + }, + want: `prebid.org wrapper`, + wantError: nil, + }, + { + name: "Empty vastXML and empty NURL", + args: args{ + vastXML: "", + NURL: "", + }, + want: "", + wantError: errors.New("invalid Vast XML"), + }, + { + name: "No Inline/Wrapper tag present", + args: args{ + vastXML: ``, + NURL: "", + }, + want: ``, + wantError: errors.New("invalid VastXML, inline/wrapper tag not found"), + }, + { + name: "Invalid Vast XML, parsing error", + args: args{ + vastXML: `iabtechlabhttp://example.com/errorhttp://example.com/track/impressionInline Simple AdIAB Sample CompanyAD CONTENT description category846500:00:16`, + NURL: "", + }, + want: ``, + wantError: errors.New("XML processing error: xml: end tag does not match start tag "), + }, + { + name: "Inline Linear vastXML, no existing event tracker", + args: args{ + vastXML: `iabtechlabhttp://example.com/errorhttp://example.com/track/impressionInline Simple AdIAB Sample CompanyAD CONTENT description category846500:00:16`, + NURL: "", + }, + want: ``, + }, + { + name: "Non Linear vastXML, no existing event tracker", + args: args{ + NURL: "", + vastXML: `iabtechlab8465`, + }, + want: ``, + }, + { + name: "Wrapper Liner vastXML", + args: args{ + NURL: "", + vastXML: `iabtechlabhttp://example.com/errorhttp://example.com/track/impression`, + }, + want: ``, + }, + { + name: "Wapper companion vastXML", + args: args{ + NURL: "", + vastXML: `iabtechlab`, + }, + want: ``, + }, + { + name: "Wapper no companion vastXML", + args: args{ + NURL: "", + vastXML: `iabtechlab`, + }, + want: ``, + }, + { + name: "Inline Non Linear empty", + args: args{ + NURL: "", + vastXML: `iabtechlaba532d16d-4d7f-4440-bd29-2ec0e693fc80iabtechlab video ad`, + }, + want: ``, + }, + { + name: "Wrapper linear and non linear", + args: args{ + NURL: "", + vastXML: `Test Ad Server`, + }, + want: ``, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ti.InjectTracker(tt.args.vastXML, tt.args.NURL) + assert.Equal(t, tt.want, got, tt.name) + if tt.wantError != nil { + assert.EqualError(t, err, tt.wantError.Error()) + } + }) + } +} + +func TestAddClickTrackingEvent(t *testing.T) { + tests := []struct { + name string + addParentTag bool + expected string + }{ + { + name: "With Parent Tag", + addParentTag: true, + expected: "", + }, + { + name: "Without Parent Tag", + addParentTag: false, + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var outputXML strings.Builder + b := macros.NewProvider(reqWrapper) + b.PopulateBidMacros(&entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + ID: "bid123", + }, + }, "testSeat") + ti := NewTrackerInjector( + macros.NewStringIndexBasedReplacer(), + b, + VASTEvents{ + VideoClicks: []string{"http://videoclicktracker.com?macro1=##PBS-BIDID##¯o2=##PBS-APPBUNDLE##¯o3=##PBS-APPBUNDLE##¯o4=##PBS-PUBDOMAIN##¯o5=##PBS-PAGEURL##¯o6=##PBS-ACCOUNTID##¯o6=##PBS-LIMITADTRACKING##¯o7=##PBS-GDPRCONSENT##¯o8=##PBS-GDPRCONSENT##¯o9=##PBS-MACRO-CUSTOMMACR1CUST1##¯o10=##PBS-MACRO-CUSTOMMACR1CUST2##"}, + }, + ) + ti.addClickTrackingEvent(&outputXML, "testCreativeId", tt.addParentTag) + assert.Equal(t, tt.expected, outputXML.String(), tt.name) + }) + } +} + +func TestAddImpressionTrackingEvent(t *testing.T) { + tests := []struct { + name string + addParentTag bool + expected string + }{ + { + name: "Add impression tag", + addParentTag: true, + expected: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var outputXML strings.Builder + b := macros.NewProvider(reqWrapper) + b.PopulateBidMacros(&entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + ID: "bid123", + }, + }, "testSeat") + ti := NewTrackerInjector( + macros.NewStringIndexBasedReplacer(), + b, + VASTEvents{ + Impressions: []string{"http://impressiontracker.com?macro1=##PBS-BIDID##¯o2=##PBS-APPBUNDLE##¯o3=##PBS-APPBUNDLE##¯o4=##PBS-PUBDOMAIN##¯o5=##PBS-PAGEURL##¯o6=##PBS-ACCOUNTID##¯o6=##PBS-LIMITADTRACKING##¯o7=##PBS-GDPRCONSENT##¯o8=##PBS-GDPRCONSENT##¯o9=##PBS-MACRO-CUSTOMMACR1CUST1##¯o10=##PBS-MACRO-CUSTOMMACR1CUST2##"}, + }, + ) + ti.addImpressionTrackingEvent(&outputXML) + assert.Equal(t, tt.expected, outputXML.String(), tt.name) + }) + } +} + +func TestAddErrorTrackingEvent(t *testing.T) { + tests := []struct { + name string + addParentTag bool + expected string + }{ + { + name: "Add impression tag", + addParentTag: true, + expected: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var outputXML strings.Builder + b := macros.NewProvider(reqWrapper) + b.PopulateBidMacros(&entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + ID: "bid123", + }, + }, "testSeat") + ti := NewTrackerInjector( + macros.NewStringIndexBasedReplacer(), + b, + VASTEvents{ + Errors: []string{"http://errortracker.com?macro1=##PBS-BIDID##¯o2=##PBS-APPBUNDLE##¯o3=##PBS-APPBUNDLE##¯o4=##PBS-PUBDOMAIN##¯o5=##PBS-PAGEURL##¯o6=##PBS-ACCOUNTID##¯o6=##PBS-LIMITADTRACKING##¯o7=##PBS-GDPRCONSENT##¯o8=##PBS-GDPRCONSENT##¯o9=##PBS-MACRO-CUSTOMMACR1CUST1##¯o10=##PBS-MACRO-CUSTOMMACR1CUST2##"}, + }, + ) + ti.addErrorTrackingEvent(&outputXML) + assert.Equal(t, tt.expected, outputXML.String(), tt.name) + }) + } +} + +func TestAddNonLinearClickTrackingEvent(t *testing.T) { + tests := []struct { + name string + addParentTag bool + expected string + }{ + { + name: "With Parent Tag", + addParentTag: true, + expected: "", + }, + { + name: "Without Parent Tag", + addParentTag: false, + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var outputXML strings.Builder + b := macros.NewProvider(reqWrapper) + b.PopulateBidMacros(&entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + ID: "bid123", + }, + }, "testSeat") + ti := NewTrackerInjector( + macros.NewStringIndexBasedReplacer(), + b, + VASTEvents{ + NonLinearClickTracking: []string{"http://nonlinearclicktracker.com?macro1=##PBS-BIDID##¯o2=##PBS-APPBUNDLE##¯o3=##PBS-APPBUNDLE##¯o4=##PBS-PUBDOMAIN##¯o5=##PBS-PAGEURL##¯o6=##PBS-ACCOUNTID##¯o6=##PBS-LIMITADTRACKING##¯o7=##PBS-GDPRCONSENT##¯o8=##PBS-GDPRCONSENT##¯o9=##PBS-MACRO-CUSTOMMACR1CUST1##¯o10=##PBS-MACRO-CUSTOMMACR1CUST2##"}, + }, + ) + ti.addNonLinearClickTrackingEvent(&outputXML, "testCreativeId", tt.addParentTag) + assert.Equal(t, tt.expected, outputXML.String(), tt.name) + }) + } +} + +func TestAddCompanionClickThroughEvent(t *testing.T) { + tests := []struct { + name string + addParentTag bool + expected string + }{ + { + name: "With Parent Tag", + addParentTag: true, + expected: "", + }, + { + name: "Without Parent Tag", + addParentTag: false, + expected: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var outputXML strings.Builder + b := macros.NewProvider(reqWrapper) + b.PopulateBidMacros(&entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + ID: "bid123", + }, + }, "testSeat") + ti := NewTrackerInjector( + macros.NewStringIndexBasedReplacer(), + b, + VASTEvents{ + CompanionClickThrough: []string{"http://companionclicktracker.com?macro1=##PBS-BIDID##¯o2=##PBS-APPBUNDLE##¯o3=##PBS-APPBUNDLE##¯o4=##PBS-PUBDOMAIN##¯o5=##PBS-PAGEURL##¯o6=##PBS-ACCOUNTID##¯o6=##PBS-LIMITADTRACKING##¯o7=##PBS-GDPRCONSENT##¯o8=##PBS-GDPRCONSENT##¯o9=##PBS-MACRO-CUSTOMMACR1CUST1##¯o10=##PBS-MACRO-CUSTOMMACR1CUST2##"}, + }, + ) + ti.addCompanionClickThroughEvent(&outputXML, "testCreativeId", tt.addParentTag) + assert.Equal(t, tt.expected, outputXML.String(), tt.name) + }) + } +} + +func TestAddTrackingEvent(t *testing.T) { + tests := []struct { + name string + addParentTag bool + expected string + }{ + { + name: "With Parent Tag", + addParentTag: true, + expected: "", + }, + { + name: "Without Parent Tag", + addParentTag: false, + expected: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var outputXML strings.Builder + b := macros.NewProvider(reqWrapper) + b.PopulateBidMacros(&entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + ID: "bid123", + }, + }, "testSeat") + ti := NewTrackerInjector( + macros.NewStringIndexBasedReplacer(), + b, + VASTEvents{ + TrackingEvents: map[string][]string{"firstQuartile": {"http://eventracker1.com?macro1=##PBS-BIDID##¯o2=##PBS-APPBUNDLE##¯o3=##PBS-APPBUNDLE##¯o4=##PBS-PUBDOMAIN##¯o5=##PBS-PAGEURL##¯o6=##PBS-ACCOUNTID##¯o6=##PBS-LIMITADTRACKING##¯o7=##PBS-GDPRCONSENT##¯o8=##PBS-GDPRCONSENT##¯o9=##PBS-MACRO-CUSTOMMACR1CUST1##¯o10=##PBS-MACRO-CUSTOMMACR1CUST2##"}}, + }, + ) + ti.addTrackingEvent(&outputXML, "testCreativeId", tt.addParentTag) + assert.Equal(t, tt.expected, outputXML.String(), tt.name) + }) + } +} + +func TestWriteTrackingEvent(t *testing.T) { + tests := []struct { + name string + urls []string + startTag string + endTag string + creativeId string + eventType string + vastEvent string + expectedXML string + }{ + { + name: "Single URL", + urls: []string{"http://tracker.com"}, + startTag: "", + endTag: "", + creativeId: "123", + eventType: "start", + vastEvent: "tracking", + expectedXML: "http://tracker.com", + }, + { + name: "Multiple URL", + urls: []string{"http://tracker1.com", "http://tracker2.com"}, + startTag: "", + endTag: "", + creativeId: "123", + eventType: "start", + vastEvent: "tracking", + expectedXML: "http://tracker1.comhttp://tracker2.com", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var outputXML strings.Builder + b := macros.NewProvider(reqWrapper) + b.PopulateBidMacros(&entities.PbsOrtbBid{ + Bid: &openrtb2.Bid{ + ID: "bid123", + }, + }, "testSeat") + ti := NewTrackerInjector( + macros.NewStringIndexBasedReplacer(), + b, + VASTEvents{ + TrackingEvents: map[string][]string{"firstQuartile": {"http://eventracker1.com?macro1=##PBS-BIDID##¯o2=##PBS-APPBUNDLE##¯o3=##PBS-APPBUNDLE##¯o4=##PBS-PUBDOMAIN##¯o5=##PBS-PAGEURL##¯o6=##PBS-ACCOUNTID##¯o6=##PBS-LIMITADTRACKING##¯o7=##PBS-GDPRCONSENT##¯o8=##PBS-GDPRCONSENT##¯o9=##PBS-MACRO-CUSTOMMACR1CUST1##¯o10=##PBS-MACRO-CUSTOMMACR1CUST2##"}}, + }, + ) + ti.writeTrackingEvent(tt.urls, &outputXML, tt.startTag, tt.endTag, tt.creativeId, tt.eventType, tt.vastEvent) + assert.Equal(t, tt.expectedXML, outputXML.String(), tt.name) + }) + } +} diff --git a/macros/macros.go b/macros/macros.go index 609e72cdec7..2b0e29d6238 100644 --- a/macros/macros.go +++ b/macros/macros.go @@ -15,6 +15,12 @@ type EndpointTemplateParams struct { AdUnit string MediaType string GvlID string + PageID string + SupplyId string + SspId string + SspID string + SeatID string + TokenID string } // UserSyncPrivacy specifies privacy policy macros, represented as strings, for user sync urls. diff --git a/macros/provider.go b/macros/provider.go index 0b0fc0de454..575aa49aa57 100644 --- a/macros/provider.go +++ b/macros/provider.go @@ -5,8 +5,8 @@ import ( "strconv" "time" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) const ( @@ -33,19 +33,19 @@ const ( CustomMacroPrefix = "PBS-MACRO-" ) -type macroProvider struct { +type MacroProvider struct { // macros map stores macros key values macros map[string]string } // NewBuilder returns the instance of macro buidler -func NewProvider(reqWrapper *openrtb_ext.RequestWrapper) *macroProvider { - macroProvider := ¯oProvider{macros: map[string]string{}} +func NewProvider(reqWrapper *openrtb_ext.RequestWrapper) *MacroProvider { + macroProvider := &MacroProvider{macros: map[string]string{}} macroProvider.populateRequestMacros(reqWrapper) return macroProvider } -func (b *macroProvider) populateRequestMacros(reqWrapper *openrtb_ext.RequestWrapper) { +func (b *MacroProvider) populateRequestMacros(reqWrapper *openrtb_ext.RequestWrapper) { b.macros[MacroKeyTimestamp] = strconv.Itoa(int(time.Now().Unix())) reqExt, err := reqWrapper.GetRequestExt() if err == nil && reqExt != nil { @@ -104,9 +104,8 @@ func (b *macroProvider) populateRequestMacros(reqWrapper *openrtb_ext.RequestWra } } - userExt, err := reqWrapper.GetUserExt() - if err == nil && userExt != nil && userExt.GetConsent() != nil { - b.macros[MacroKeyConsent] = *userExt.GetConsent() + if reqWrapper.User != nil && len(reqWrapper.User.Consent) > 0 { + b.macros[MacroKeyConsent] = reqWrapper.User.Consent } if reqWrapper.Device != nil && reqWrapper.Device.Lmt != nil { b.macros[MacroKeyLmtTracking] = strconv.Itoa(int(*reqWrapper.Device.Lmt)) @@ -114,11 +113,11 @@ func (b *macroProvider) populateRequestMacros(reqWrapper *openrtb_ext.RequestWra } -func (b *macroProvider) GetMacro(key string) string { +func (b *MacroProvider) GetMacro(key string) string { return url.QueryEscape(b.macros[key]) } -func (b *macroProvider) PopulateBidMacros(bid *entities.PbsOrtbBid, seat string) { +func (b *MacroProvider) PopulateBidMacros(bid *entities.PbsOrtbBid, seat string) { if bid.Bid != nil { if bid.GeneratedBidID != "" { b.macros[MacroKeyBidID] = bid.GeneratedBidID @@ -129,7 +128,7 @@ func (b *macroProvider) PopulateBidMacros(bid *entities.PbsOrtbBid, seat string) b.macros[MacroKeyBidder] = seat } -func (b *macroProvider) PopulateEventMacros(vastCreativeID, eventType, vastEvent string) { +func (b *MacroProvider) PopulateEventMacros(vastCreativeID, eventType, vastEvent string) { b.macros[MacroKeyVastCRTID] = vastCreativeID b.macros[MacroKeyEventType] = eventType b.macros[MacroKeyVastEvent] = vastEvent diff --git a/macros/provider_test.go b/macros/provider_test.go index b6465a7f2e6..795fae9c412 100644 --- a/macros/provider_test.go +++ b/macros/provider_test.go @@ -3,9 +3,9 @@ package macros import ( "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -132,7 +132,7 @@ func TestPopulateRequestMacros(t *testing.T) { args: args{ reqWrapper: &openrtb_ext.RequestWrapper{ BidRequest: &openrtb2.BidRequest{ - User: &openrtb2.User{Ext: []byte(`{"consent":"1" }`)}, + User: &openrtb2.User{Consent: "1", Ext: []byte(`{"consent":"2" }`)}, Ext: []byte(`{"prebid":{"integration":"testIntegration"}}`), }, }, @@ -189,7 +189,7 @@ func TestPopulateRequestMacros(t *testing.T) { Device: &openrtb2.Device{ Lmt: &lmt, }, - User: &openrtb2.User{Ext: []byte(`{"consent":"1" }`)}, + User: &openrtb2.User{Consent: "1", Ext: []byte(`{"consent":"2" }`)}, Ext: []byte(`{"prebid":{"channel": {"name":"test1"},"macros":{"CUSTOMMACR1":"value1"}}}`), }, }, @@ -199,7 +199,7 @@ func TestPopulateRequestMacros(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - b := ¯oProvider{ + b := &MacroProvider{ macros: map[string]string{}, } b.populateRequestMacros(tt.args.reqWrapper) @@ -300,7 +300,7 @@ func TestPopulateBidMacros(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - b := ¯oProvider{ + b := &MacroProvider{ macros: map[string]string{}, } b.PopulateBidMacros(tt.args.bid, tt.args.seat) @@ -401,7 +401,7 @@ func TestPopulateEventMacros(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - b := ¯oProvider{ + b := &MacroProvider{ macros: map[string]string{}, } b.PopulateEventMacros(tt.args.vastCreativeID, tt.args.eventType, tt.args.vastEvent) diff --git a/macros/replacer.go b/macros/replacer.go index e1a13d27636..a28be0cd21b 100644 --- a/macros/replacer.go +++ b/macros/replacer.go @@ -1,7 +1,9 @@ package macros +import "strings" + type Replacer interface { // Replace the macros and returns replaced string // if any error the error will be returned - Replace(url string, macroProvider *macroProvider) (string, error) + Replace(result *strings.Builder, url string, macroProvider *MacroProvider) } diff --git a/macros/string_index_based_replacer.go b/macros/string_index_based_replacer.go index 32748c1ed76..da0c89322e2 100644 --- a/macros/string_index_based_replacer.go +++ b/macros/string_index_based_replacer.go @@ -65,14 +65,12 @@ func constructTemplate(url string) urlMetaTemplate { // If input string is not found in cache then template metadata will be created. // Iterates over start and end indexes of the template arrays and extracts macro name from the input string. // Gets the value of the extracted macro from the macroProvider. Replaces macro with corresponding value. -func (s *stringIndexBasedReplacer) Replace(url string, macroProvider *macroProvider) (string, error) { - tmplt := s.getTemplate(url) - - var result strings.Builder +func (s *stringIndexBasedReplacer) Replace(result *strings.Builder, url string, macroProvider *MacroProvider) { + template := s.getTemplate(url) currentIndex := 0 delimLen := len(delimiter) - for i, index := range tmplt.startingIndices { - macro := url[index : tmplt.endingIndices[i]+1] + for i, index := range template.startingIndices { + macro := url[index : template.endingIndices[i]+1] // copy prev part result.WriteString(url[currentIndex : index-delimLen]) value := macroProvider.GetMacro(macro) @@ -82,7 +80,6 @@ func (s *stringIndexBasedReplacer) Replace(url string, macroProvider *macroProvi currentIndex = index + len(macro) + delimLen } result.WriteString(url[currentIndex:]) - return result.String(), nil } func (s *stringIndexBasedReplacer) getTemplate(url string) urlMetaTemplate { diff --git a/macros/string_index_based_replacer_test.go b/macros/string_index_based_replacer_test.go index 97379a6d965..515640d4ff2 100644 --- a/macros/string_index_based_replacer_test.go +++ b/macros/string_index_based_replacer_test.go @@ -1,11 +1,12 @@ package macros import ( + "strings" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/exchange/entities" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/exchange/entities" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -13,7 +14,7 @@ func TestStringIndexBasedReplace(t *testing.T) { type args struct { url string - getMacroProvider func() *macroProvider + getMacroProvider func() *MacroProvider } tests := []struct { name string @@ -25,69 +26,61 @@ func TestStringIndexBasedReplace(t *testing.T) { name: "success", args: args{ url: "http://tracker.com?macro1=##PBS-BIDID##¯o2=##PBS-APPBUNDLE##¯o3=##PBS-DOMAIN##¯o4=##PBS-PUBDOMAIN##¯o5=##PBS-PAGEURL##¯o6=##PBS-ACCOUNTID##¯o7=##PBS-LIMITADTRACKING##¯o8=##PBS-GDPRCONSENT##¯o9=##PBS-MACRO-CUSTOMMACR1##¯o10=##PBS-BIDDER##¯o11=##PBS-INTEGRATION##¯o12=##PBS-VASTCRTID##¯o15=##PBS-AUCTIONID##¯o16=##PBS-CHANNEL##¯o17=##PBS-EVENTTYPE##¯o18=##PBS-VASTEVENT##", - getMacroProvider: func() *macroProvider { + getMacroProvider: func() *MacroProvider { macroProvider := NewProvider(req) macroProvider.PopulateBidMacros(&entities.PbsOrtbBid{Bid: bid}, "test") macroProvider.PopulateEventMacros("123", "vast", "firstQuartile") return macroProvider }, }, - want: "http://tracker.com?macro1=bidId123¯o2=testbundle¯o3=testdomain¯o4=publishertestdomain¯o5=pageurltest¯o6=testpublisherID¯o7=10¯o8=yes¯o9=value1¯o10=test¯o11=¯o12=123¯o15=123¯o16=test1¯o17=vast¯o18=firstQuartile", - wantErr: false, + want: "http://tracker.com?macro1=bidId123¯o2=testbundle¯o3=testdomain¯o4=publishertestdomain¯o5=pageurltest¯o6=testpublisherID¯o7=10¯o8=yes¯o9=value1¯o10=test¯o11=¯o12=123¯o15=123¯o16=test1¯o17=vast¯o18=firstQuartile", }, { name: "url does not have macro", args: args{ url: "http://tracker.com", - getMacroProvider: func() *macroProvider { + getMacroProvider: func() *MacroProvider { macroProvider := NewProvider(req) macroProvider.PopulateBidMacros(&entities.PbsOrtbBid{Bid: bid}, "test") macroProvider.PopulateEventMacros("123", "vast", "firstQuartile") return macroProvider }, }, - want: "http://tracker.com", - wantErr: false, + want: "http://tracker.com", }, { name: "macro not found", args: args{ url: "http://tracker.com?macro1=##PBS-test1##", - getMacroProvider: func() *macroProvider { + getMacroProvider: func() *MacroProvider { macroProvider := NewProvider(&openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}}) macroProvider.PopulateBidMacros(&entities.PbsOrtbBid{Bid: bid}, "test") macroProvider.PopulateEventMacros("123", "vast", "firstQuartile") return macroProvider }, }, - want: "http://tracker.com?macro1=", - wantErr: false, + want: "http://tracker.com?macro1=", }, { name: "tracker url is empty", args: args{ url: "", - getMacroProvider: func() *macroProvider { + getMacroProvider: func() *MacroProvider { macroProvider := NewProvider(&openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}}) macroProvider.PopulateBidMacros(&entities.PbsOrtbBid{Bid: bid}, "test") macroProvider.PopulateEventMacros("123", "vast", "firstQuartile") return macroProvider }, }, - want: "", - wantErr: false, + want: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { replacer := NewStringIndexBasedReplacer() - got, err := replacer.Replace(tt.args.url, tt.args.getMacroProvider()) - if tt.wantErr { - assert.Error(t, err, tt.name) - } else { - assert.NoError(t, err, tt.name) - assert.Equal(t, tt.want, got, tt.name) - } + builder := strings.Builder{} + replacer.Replace(&builder, tt.args.url, tt.args.getMacroProvider()) + assert.Equal(t, tt.want, builder.String(), tt.name) }) } } @@ -123,7 +116,7 @@ var req *openrtb_ext.RequestWrapper = &openrtb_ext.RequestWrapper{ Device: &openrtb2.Device{ Lmt: &lmt, }, - User: &openrtb2.User{Ext: []byte(`{"consent":"yes" }`)}, + User: &openrtb2.User{Consent: "yes", Ext: []byte(`{"consent":"no" }`)}, Ext: []byte(`{"prebid":{"channel": {"name":"test1"},"macros":{"CUSTOMMACR1":"value1","CUSTOMMACR2":"value2","CUSTOMMACR3":"value3"}}}`), }, } @@ -132,15 +125,13 @@ var bid *openrtb2.Bid = &openrtb2.Bid{ID: "bidId123", CID: "campaign_1", CrID: " func BenchmarkStringIndexBasedReplacer(b *testing.B) { replacer := NewStringIndexBasedReplacer() + builder := &strings.Builder{} for n := 0; n < b.N; n++ { for _, url := range benchmarkURL { macroProvider := NewProvider(req) macroProvider.PopulateBidMacros(&entities.PbsOrtbBid{Bid: bid}, "test") macroProvider.PopulateEventMacros("123", "vast", "firstQuartile") - _, err := replacer.Replace(url, macroProvider) - if err != nil { - b.Errorf("Fail to replace macro in tracker") - } + replacer.Replace(builder, url, macroProvider) } } } diff --git a/main.go b/main.go index a83266665f0..0063b4ee0b6 100644 --- a/main.go +++ b/main.go @@ -2,25 +2,26 @@ package main import ( "flag" - "math/rand" "net/http" "path/filepath" "runtime" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/router" - "github.com/prebid/prebid-server/server" - "github.com/prebid/prebid-server/util/task" + jsoniter "github.com/json-iterator/go" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/router" + "github.com/prebid/prebid-server/v3/server" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/task" "github.com/golang/glog" "github.com/spf13/viper" ) func init() { - rand.Seed(time.Now().UnixNano()) + jsoniter.RegisterExtension(&jsonutil.RawMessageExtension{}) } func main() { @@ -77,7 +78,9 @@ func serve(cfg *config.Configuration) error { } corsRouter := router.SupportCORS(r) - server.Listen(cfg, router.NoCache{Handler: corsRouter}, router.Admin(currencyConverter, fetchingInterval), r.MetricsEngine) + if err := server.Listen(cfg, router.NoCache{Handler: corsRouter}, router.Admin(currencyConverter, fetchingInterval), r.MetricsEngine); err != nil { + glog.Fatalf("prebid-server returned an error: %v", err) + } r.Shutdown() return nil diff --git a/main_test.go b/main_test.go index 25812ba96ab..494e6d67746 100644 --- a/main_test.go +++ b/main_test.go @@ -4,7 +4,7 @@ import ( "os" "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v3/config" "github.com/stretchr/testify/assert" "github.com/spf13/viper" diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go index d5cb00344ec..dcb330c47f9 100644 --- a/metrics/config/metrics.go +++ b/metrics/config/metrics.go @@ -3,10 +3,10 @@ package config import ( "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - prometheusmetrics "github.com/prebid/prebid-server/metrics/prometheus" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/metrics" + prometheusmetrics "github.com/prebid/prebid-server/v3/metrics/prometheus" + "github.com/prebid/prebid-server/v3/openrtb_ext" gometrics "github.com/rcrowley/go-metrics" influxdb "github.com/vrischmann/go-metrics-influxdb" ) @@ -266,6 +266,13 @@ func (me *MultiMetricsEngine) RecordRequestPrivacy(privacy metrics.PrivacyLabels } } +// RecordAdapterBuyerUIDScrubbed across all engines +func (me *MultiMetricsEngine) RecordAdapterBuyerUIDScrubbed(adapter openrtb_ext.BidderName) { + for _, thisME := range *me { + thisME.RecordAdapterBuyerUIDScrubbed(adapter) + } +} + // RecordAdapterGDPRRequestBlocked across all engines func (me *MultiMetricsEngine) RecordAdapterGDPRRequestBlocked(adapter openrtb_ext.BidderName) { for _, thisME := range *me { @@ -484,6 +491,10 @@ func (me *NilMetricsEngine) RecordTimeoutNotice(success bool) { func (me *NilMetricsEngine) RecordRequestPrivacy(privacy metrics.PrivacyLabels) { } +// RecordAdapterBuyerUIDScrubbed as a noop +func (me *NilMetricsEngine) RecordAdapterBuyerUIDScrubbed(adapter openrtb_ext.BidderName) { +} + // RecordAdapterGDPRRequestBlocked as a noop func (me *NilMetricsEngine) RecordAdapterGDPRRequestBlocked(adapter openrtb_ext.BidderName) { } diff --git a/metrics/config/metrics_test.go b/metrics/config/metrics_test.go index 1eae1a25545..6060b692bfe 100644 --- a/metrics/config/metrics_test.go +++ b/metrics/config/metrics_test.go @@ -6,9 +6,9 @@ import ( "testing" "time" - mainConfig "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + mainConfig "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" gometrics "github.com/rcrowley/go-metrics" ) @@ -39,17 +39,14 @@ func TestGoMetricsEngine(t *testing.T) { } } -// Test the multiengine func TestMultiMetricsEngine(t *testing.T) { cfg := mainConfig.Configuration{} cfg.Metrics.Influxdb.Host = "localhost" adapterList := openrtb_ext.CoreBidderNames() goEngine := metrics.NewMetrics(gometrics.NewPrefixedRegistry("prebidserver."), adapterList, mainConfig.DisabledMetrics{}, nil, modulesStages) - engineList := make(MultiMetricsEngine, 2) - engineList[0] = goEngine - engineList[1] = &NilMetricsEngine{} - var metricsEngine metrics.MetricsEngine - metricsEngine = &engineList + metricsEngine := make(MultiMetricsEngine, 2) + metricsEngine[0] = goEngine + metricsEngine[1] = &NilMetricsEngine{} labels := metrics.Labels{ Source: metrics.DemandWeb, RType: metrics.ReqTypeORTB2Web, @@ -109,23 +106,23 @@ func TestMultiMetricsEngine(t *testing.T) { metricsEngine.RecordModuleExecutionError(module) metricsEngine.RecordModuleTimeout(module) } - labelsBlacklist := []metrics.Labels{ + labelsBlocked := []metrics.Labels{ { Source: metrics.DemandWeb, RType: metrics.ReqTypeAMP, PubID: "test2", CookieFlag: metrics.CookieFlagYes, - RequestStatus: metrics.RequestStatusBlacklisted, + RequestStatus: metrics.RequestStatusBlockedApp, }, { Source: metrics.DemandWeb, RType: metrics.ReqTypeVideo, PubID: "test2", CookieFlag: metrics.CookieFlagYes, - RequestStatus: metrics.RequestStatusBlacklisted, + RequestStatus: metrics.RequestStatusBlockedApp, }, } - for _, label := range labelsBlacklist { + for _, label := range labelsBlocked { metricsEngine.RecordRequest(label) } impTypeLabels.BannerImps = false @@ -143,6 +140,7 @@ func TestMultiMetricsEngine(t *testing.T) { metricsEngine.RecordStoredImpCacheResult(metrics.CacheHit, 5) metricsEngine.RecordAccountCacheResult(metrics.CacheHit, 6) + metricsEngine.RecordAdapterBuyerUIDScrubbed(openrtb_ext.BidderAppnexus) metricsEngine.RecordAdapterGDPRRequestBlocked(openrtb_ext.BidderAppnexus) metricsEngine.RecordRequestQueueTime(false, metrics.ReqTypeVideo, time.Duration(1)) @@ -150,14 +148,14 @@ func TestMultiMetricsEngine(t *testing.T) { //Make the metrics engine, instantiated here with goEngine, fill its RequestStatuses[RequestType][metrics.RequestStatusXX] with the new boolean values added to metrics.Labels VerifyMetrics(t, "RequestStatuses.OpenRTB2.OK", goEngine.RequestStatuses[metrics.ReqTypeORTB2Web][metrics.RequestStatusOK].Count(), 5) VerifyMetrics(t, "RequestStatuses.AMP.OK", goEngine.RequestStatuses[metrics.ReqTypeAMP][metrics.RequestStatusOK].Count(), 0) - VerifyMetrics(t, "RequestStatuses.AMP.BlacklistedAcctOrApp", goEngine.RequestStatuses[metrics.ReqTypeAMP][metrics.RequestStatusBlacklisted].Count(), 1) + VerifyMetrics(t, "RequestStatuses.AMP.BlockedApp", goEngine.RequestStatuses[metrics.ReqTypeAMP][metrics.RequestStatusBlockedApp].Count(), 1) VerifyMetrics(t, "RequestStatuses.Video.OK", goEngine.RequestStatuses[metrics.ReqTypeVideo][metrics.RequestStatusOK].Count(), 0) VerifyMetrics(t, "RequestStatuses.Video.Error", goEngine.RequestStatuses[metrics.ReqTypeVideo][metrics.RequestStatusErr].Count(), 0) VerifyMetrics(t, "RequestStatuses.Video.BadInput", goEngine.RequestStatuses[metrics.ReqTypeVideo][metrics.RequestStatusBadInput].Count(), 0) - VerifyMetrics(t, "RequestStatuses.Video.BlacklistedAcctOrApp", goEngine.RequestStatuses[metrics.ReqTypeVideo][metrics.RequestStatusBlacklisted].Count(), 1) + VerifyMetrics(t, "RequestStatuses.Video.BlockedApp", goEngine.RequestStatuses[metrics.ReqTypeVideo][metrics.RequestStatusBlockedApp].Count(), 1) VerifyMetrics(t, "RequestStatuses.OpenRTB2.Error", goEngine.RequestStatuses[metrics.ReqTypeORTB2Web][metrics.RequestStatusErr].Count(), 0) VerifyMetrics(t, "RequestStatuses.OpenRTB2.BadInput", goEngine.RequestStatuses[metrics.ReqTypeORTB2Web][metrics.RequestStatusBadInput].Count(), 0) - VerifyMetrics(t, "RequestStatuses.OpenRTB2.BlacklistedAcctOrApp", goEngine.RequestStatuses[metrics.ReqTypeORTB2Web][metrics.RequestStatusBlacklisted].Count(), 0) + VerifyMetrics(t, "RequestStatuses.OpenRTB2.BlockedApp", goEngine.RequestStatuses[metrics.ReqTypeORTB2Web][metrics.RequestStatusBlockedApp].Count(), 0) VerifyMetrics(t, "ImpsTypeBanner", goEngine.ImpsTypeBanner.Count(), 5) VerifyMetrics(t, "ImpsTypeVideo", goEngine.ImpsTypeVideo.Count(), 3) @@ -188,6 +186,7 @@ func TestMultiMetricsEngine(t *testing.T) { VerifyMetrics(t, "StoredImpCache.Hit", goEngine.StoredImpCacheMeter[metrics.CacheHit].Count(), 5) VerifyMetrics(t, "AccountCache.Hit", goEngine.AccountCacheMeter[metrics.CacheHit].Count(), 6) + VerifyMetrics(t, "AdapterMetrics.appNexus.BuyerUIDScrubbed", goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderAppnexus))].BuyerUIDScrubbed.Count(), 1) VerifyMetrics(t, "AdapterMetrics.appNexus.GDPRRequestBlocked", goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderAppnexus))].GDPRRequestBlocked.Count(), 1) // verify that each module has its own metric recorded diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go index 13cc022c9c8..428b1891e69 100644 --- a/metrics/go_metrics.go +++ b/metrics/go_metrics.go @@ -7,8 +7,8 @@ import ( "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" metrics "github.com/rcrowley/go-metrics" ) @@ -99,6 +99,7 @@ type AdapterMetrics struct { ConnCreated metrics.Counter ConnReused metrics.Counter ConnWaitTime metrics.Timer + BuyerUIDScrubbed metrics.Meter GDPRRequestBlocked metrics.Meter BidValidationCreativeSizeErrorMeter metrics.Meter @@ -406,6 +407,9 @@ func makeBlankAdapterMetrics(disabledMetrics config.DisabledMetrics) *AdapterMet newAdapter.ConnReused = metrics.NilCounter{} newAdapter.ConnWaitTime = &metrics.NilTimer{} } + if !disabledMetrics.AdapterBuyerUIDScrubbed { + newAdapter.BuyerUIDScrubbed = blankMeter + } if !disabledMetrics.AdapterGDPRRequestBlocked { newAdapter.GDPRRequestBlocked = blankMeter } @@ -484,6 +488,7 @@ func registerAdapterMetrics(registry metrics.Registry, adapterOrAccount string, am.BidsReceivedMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.bids_received", adapterOrAccount, exchange), registry) } am.PanicMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.requests.panic", adapterOrAccount, exchange), registry) + am.BuyerUIDScrubbed = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.buyeruid_scrubbed", adapterOrAccount, exchange), registry) am.GDPRRequestBlocked = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.gdpr_request_blocked", adapterOrAccount, exchange), registry) am.BidValidationCreativeSizeErrorMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.response.validation.size.err", adapterOrAccount, exchange), registry) @@ -926,6 +931,21 @@ func (me *Metrics) RecordRequestPrivacy(privacy PrivacyLabels) { } } +func (me *Metrics) RecordAdapterBuyerUIDScrubbed(adapterName openrtb_ext.BidderName) { + adapterStr := adapterName.String() + if me.MetricsDisabled.AdapterBuyerUIDScrubbed { + return + } + + am, ok := me.AdapterMetrics[strings.ToLower(adapterStr)] + if !ok { + glog.Errorf("Trying to log adapter buyeruid scrubbed metric for %s: adapter not found", adapterStr) + return + } + + am.BuyerUIDScrubbed.Mark(1) +} + func (me *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) { adapterStr := string(adapterName) if me.MetricsDisabled.AdapterGDPRRequestBlocked { diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index 08ee65f52ac..791b4ee0ec8 100644 --- a/metrics/go_metrics_test.go +++ b/metrics/go_metrics_test.go @@ -5,8 +5,8 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" metrics "github.com/rcrowley/go-metrics" "github.com/stretchr/testify/assert" ) @@ -73,7 +73,7 @@ func TestNewMetrics(t *testing.T) { ensureContains(t, registry, "syncer.foo.request.ok", m.SyncerRequestsMeter["foo"][SyncerCookieSyncOK]) ensureContains(t, registry, "syncer.foo.request.privacy_blocked", m.SyncerRequestsMeter["foo"][SyncerCookieSyncPrivacyBlocked]) ensureContains(t, registry, "syncer.foo.request.already_synced", m.SyncerRequestsMeter["foo"][SyncerCookieSyncAlreadySynced]) - ensureContains(t, registry, "syncer.foo.request.type_not_supported", m.SyncerRequestsMeter["foo"][SyncerCookieSyncTypeNotSupported]) + ensureContains(t, registry, "syncer.foo.request.rejected_by_filter", m.SyncerRequestsMeter["foo"][SyncerCookieSyncRejectedByFilter]) ensureContains(t, registry, "syncer.foo.set.ok", m.SyncerSetsMeter["foo"][SyncerSetUidOK]) ensureContains(t, registry, "syncer.foo.set.cleared", m.SyncerSetsMeter["foo"][SyncerSetUidCleared]) @@ -790,43 +790,87 @@ func TestRecordRequestPrivacy(t *testing.T) { assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionV2].Count(), int64(1), "TCF V2") } +func TestRecordAdapterBuyerUIDScrubbed(t *testing.T) { + var fakeBidder openrtb_ext.BidderName = "fooAdvertising" + adapter := "AnyName" + lowerCaseAdapterName := "anyname" + + tests := []struct { + name string + metricsDisabled bool + adapterName openrtb_ext.BidderName + expectedCount int64 + }{ + { + name: "enabled_bidder_found", + metricsDisabled: false, + adapterName: openrtb_ext.BidderName(adapter), + expectedCount: 1, + }, + { + name: "enabled_bidder_not_found", + metricsDisabled: false, + adapterName: fakeBidder, + expectedCount: 0, + }, + { + name: "disabled", + metricsDisabled: true, + adapterName: openrtb_ext.BidderName(adapter), + expectedCount: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + registry := metrics.NewRegistry() + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AdapterBuyerUIDScrubbed: tt.metricsDisabled}, nil, nil) + + m.RecordAdapterBuyerUIDScrubbed(tt.adapterName) + + assert.Equal(t, tt.expectedCount, m.AdapterMetrics[lowerCaseAdapterName].BuyerUIDScrubbed.Count()) + }) + } +} + func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { var fakeBidder openrtb_ext.BidderName = "fooAdvertising" adapter := "AnyName" lowerCaseAdapterName := "anyname" tests := []struct { - description string + name string metricsDisabled bool adapterName openrtb_ext.BidderName expectedCount int64 }{ { - description: "", + name: "enabled_bidder_found", metricsDisabled: false, adapterName: openrtb_ext.BidderName(adapter), expectedCount: 1, }, { - description: "", + name: "enabled_bidder_not_found", metricsDisabled: false, adapterName: fakeBidder, expectedCount: 0, }, { - description: "", + name: "disabled", metricsDisabled: true, adapterName: openrtb_ext.BidderName(adapter), expectedCount: 0, }, } for _, tt := range tests { - registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AdapterGDPRRequestBlocked: tt.metricsDisabled}, nil, nil) + t.Run(tt.name, func(t *testing.T) { + registry := metrics.NewRegistry() + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderName(adapter)}, config.DisabledMetrics{AdapterGDPRRequestBlocked: tt.metricsDisabled}, nil, nil) - m.RecordAdapterGDPRRequestBlocked(tt.adapterName) + m.RecordAdapterGDPRRequestBlocked(tt.adapterName) - assert.Equal(t, tt.expectedCount, m.AdapterMetrics[lowerCaseAdapterName].GDPRRequestBlocked.Count(), tt.description) + assert.Equal(t, tt.expectedCount, m.AdapterMetrics[lowerCaseAdapterName].GDPRRequestBlocked.Count()) + }) } } @@ -864,7 +908,7 @@ func TestRecordSyncerRequest(t *testing.T) { assert.Equal(t, m.SyncerRequestsMeter["foo"][SyncerCookieSyncOK].Count(), int64(1)) assert.Equal(t, m.SyncerRequestsMeter["foo"][SyncerCookieSyncPrivacyBlocked].Count(), int64(0)) assert.Equal(t, m.SyncerRequestsMeter["foo"][SyncerCookieSyncAlreadySynced].Count(), int64(0)) - assert.Equal(t, m.SyncerRequestsMeter["foo"][SyncerCookieSyncTypeNotSupported].Count(), int64(0)) + assert.Equal(t, m.SyncerRequestsMeter["foo"][SyncerCookieSyncRejectedByFilter].Count(), int64(0)) } func TestRecordSetUid(t *testing.T) { diff --git a/metrics/metrics.go b/metrics/metrics.go index 0c3025bd296..45484cab7fd 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -3,7 +3,7 @@ package metrics import ( "time" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // Labels defines the labels that can be attached to the metrics. @@ -234,7 +234,7 @@ const ( RequestStatusBadInput RequestStatus = "badinput" RequestStatusErr RequestStatus = "err" RequestStatusNetworkErr RequestStatus = "networkerr" - RequestStatusBlacklisted RequestStatus = "blacklistedacctorapp" + RequestStatusBlockedApp RequestStatus = "blockedapp" RequestStatusQueueTimeout RequestStatus = "queuetimeout" RequestStatusAccountConfigErr RequestStatus = "acctconfigerr" ) @@ -245,7 +245,7 @@ func RequestStatuses() []RequestStatus { RequestStatusBadInput, RequestStatusErr, RequestStatusNetworkErr, - RequestStatusBlacklisted, + RequestStatusBlockedApp, RequestStatusQueueTimeout, RequestStatusAccountConfigErr, } @@ -361,7 +361,7 @@ const ( SyncerCookieSyncOK SyncerCookieSyncStatus = "ok" SyncerCookieSyncPrivacyBlocked SyncerCookieSyncStatus = "privacy_blocked" SyncerCookieSyncAlreadySynced SyncerCookieSyncStatus = "already_synced" - SyncerCookieSyncTypeNotSupported SyncerCookieSyncStatus = "type_not_supported" + SyncerCookieSyncRejectedByFilter SyncerCookieSyncStatus = "rejected_by_filter" ) // SyncerRequestStatuses returns possible syncer statuses. @@ -370,7 +370,7 @@ func SyncerRequestStatuses() []SyncerCookieSyncStatus { SyncerCookieSyncOK, SyncerCookieSyncPrivacyBlocked, SyncerCookieSyncAlreadySynced, - SyncerCookieSyncTypeNotSupported, + SyncerCookieSyncRejectedByFilter, } } @@ -455,6 +455,7 @@ type MetricsEngine interface { RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration) RecordTimeoutNotice(success bool) RecordRequestPrivacy(privacy PrivacyLabels) + RecordAdapterBuyerUIDScrubbed(adapterName openrtb_ext.BidderName) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) RecordDebugRequest(debugEnabled bool, pubId string) RecordStoredResponse(pubId string) diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go index eb092f0d972..e979c6db72e 100644 --- a/metrics/metrics_mock.go +++ b/metrics/metrics_mock.go @@ -3,7 +3,7 @@ package metrics import ( "time" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/mock" ) @@ -156,6 +156,11 @@ func (me *MetricsEngineMock) RecordRequestPrivacy(privacy PrivacyLabels) { me.Called(privacy) } +// RecordAdapterBuyerUIDScrubbed mock +func (me *MetricsEngineMock) RecordAdapterBuyerUIDScrubbed(adapterName openrtb_ext.BidderName) { + me.Called(adapterName) +} + // RecordAdapterGDPRRequestBlocked mock func (me *MetricsEngineMock) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) { me.Called(adapterName) diff --git a/metrics/prometheus/preload.go b/metrics/prometheus/preload.go index db40feabbd8..c59dfeece02 100644 --- a/metrics/prometheus/preload.go +++ b/metrics/prometheus/preload.go @@ -1,8 +1,8 @@ package prometheusmetrics import ( - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/prometheus/client_golang/prometheus" ) @@ -229,6 +229,12 @@ func preloadLabelValues(m *Metrics, syncerKeys []string, moduleStageNames map[st versionLabel: tcfVersionValues, }) + if !m.metricsDisabled.AdapterBuyerUIDScrubbed { + preloadLabelValuesForCounter(m.adapterScrubbedBuyerUIDs, map[string][]string{ + adapterLabel: adapterValues, + }) + } + if !m.metricsDisabled.AdapterGDPRRequestBlocked { preloadLabelValuesForCounter(m.adapterGDPRBlockedRequests, map[string][]string{ adapterLabel: adapterValues, diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index bd63997f0d2..7273a97f07d 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -6,9 +6,9 @@ import ( "strings" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/prometheus/client_golang/prometheus" promCollector "github.com/prometheus/client_golang/prometheus/collectors" ) @@ -70,6 +70,7 @@ type Metrics struct { adapterReusedConnections *prometheus.CounterVec adapterCreatedConnections *prometheus.CounterVec adapterConnectionWaitTime *prometheus.HistogramVec + adapterScrubbedBuyerUIDs *prometheus.CounterVec adapterGDPRBlockedRequests *prometheus.CounterVec adapterBidResponseValidationSizeError *prometheus.CounterVec adapterBidResponseValidationSizeWarn *prometheus.CounterVec @@ -334,6 +335,12 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet "Count of total requests to Prebid Server where the LMT flag was set by source", []string{sourceLabel}) + if !metrics.metricsDisabled.AdapterBuyerUIDScrubbed { + metrics.adapterScrubbedBuyerUIDs = newCounter(cfg, reg, + "adapter_buyeruids_scrubbed", + "Count of total bidder requests with a scrubbed buyeruid due to a privacy policy", + []string{adapterLabel}) + } if !metrics.metricsDisabled.AdapterGDPRRequestBlocked { metrics.adapterGDPRBlockedRequests = newCounter(cfg, reg, "adapter_gdpr_requests_blocked", @@ -952,6 +959,16 @@ func (m *Metrics) RecordRequestPrivacy(privacy metrics.PrivacyLabels) { } } +func (m *Metrics) RecordAdapterBuyerUIDScrubbed(adapterName openrtb_ext.BidderName) { + if m.metricsDisabled.AdapterBuyerUIDScrubbed { + return + } + + m.adapterScrubbedBuyerUIDs.With(prometheus.Labels{ + adapterLabel: strings.ToLower(string(adapterName)), + }).Inc() +} + func (m *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) { if m.metricsDisabled.AdapterGDPRRequestBlocked { return diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index a2b0e84d2ea..3ec5969bb6f 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -5,9 +5,9 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/assert" @@ -64,7 +64,7 @@ func TestMetricCountGatekeeping(t *testing.T) { // Verify Per-Adapter Cardinality // - This assertion provides a warning for newly added adapter metrics. Threre are 40+ adapters which makes the // cost of new per-adapter metrics rather expensive. Thought should be given when adding new per-adapter metrics. - assert.True(t, perAdapterCardinalityCount <= 30, "Per-Adapter Cardinality count equals %d \n", perAdapterCardinalityCount) + assert.True(t, perAdapterCardinalityCount <= 31, "Per-Adapter Cardinality count equals %d \n", perAdapterCardinalityCount) } func TestConnectionMetrics(t *testing.T) { @@ -141,7 +141,7 @@ func TestConnectionMetrics(t *testing.T) { func TestRequestMetric(t *testing.T) { m := createMetricsForTesting() requestType := metrics.ReqTypeORTB2Web - requestStatus := metrics.RequestStatusBlacklisted + requestStatus := metrics.RequestStatusBlockedApp m.RecordRequest(metrics.Labels{ RType: requestType, @@ -285,7 +285,7 @@ func TestRequestMetricWithoutCookie(t *testing.T) { performTest := func(m *Metrics, cookieFlag metrics.CookieFlag) { m.RecordRequest(metrics.Labels{ RType: requestType, - RequestStatus: metrics.RequestStatusBlacklisted, + RequestStatus: metrics.RequestStatusBlockedApp, CookieFlag: cookieFlag, }) } @@ -337,7 +337,7 @@ func TestAccountMetric(t *testing.T) { performTest := func(m *Metrics, pubID string) { m.RecordRequest(metrics.Labels{ RType: metrics.ReqTypeORTB2Web, - RequestStatus: metrics.RequestStatusBlacklisted, + RequestStatus: metrics.RequestStatusBlockedApp, PubID: pubID, }) } @@ -1235,7 +1235,7 @@ func TestRecordSyncerRequestMetric(t *testing.T) { label: "already_synced", }, { - status: metrics.SyncerCookieSyncTypeNotSupported, + status: metrics.SyncerCookieSyncRejectedByFilter, label: "type_not_supported", }, } @@ -1635,6 +1635,7 @@ func TestDisabledMetrics(t *testing.T) { Namespace: "prebid", Subsystem: "server", }, config.DisabledMetrics{ + AdapterBuyerUIDScrubbed: true, AdapterConnectionMetrics: true, AdapterGDPRRequestBlocked: true, }, @@ -1642,6 +1643,7 @@ func TestDisabledMetrics(t *testing.T) { // Assert counter vector was not initialized assert.Nil(t, prometheusMetrics.adapterReusedConnections, "Counter Vector adapterReusedConnections should be nil") + assert.Nil(t, prometheusMetrics.adapterScrubbedBuyerUIDs, "Counter Vector adapterScrubbedBuyerUIDs should be nil") assert.Nil(t, prometheusMetrics.adapterCreatedConnections, "Counter Vector adapterCreatedConnections should be nil") assert.Nil(t, prometheusMetrics.adapterConnectionWaitTime, "Counter Vector adapterConnectionWaitTime should be nil") assert.Nil(t, prometheusMetrics.adapterGDPRBlockedRequests, "Counter Vector adapterGDPRBlockedRequests should be nil") @@ -1755,7 +1757,7 @@ func getHistogramFromHistogramVecByTwoKeys(histogram *prometheus.HistogramVec, l processMetrics(histogram, func(m dto.Metric) { for ind, label := range m.GetLabel() { if label.GetName() == label1Key && label.GetValue() == label1Value { - valInd := ind + var valInd int if ind == 1 { valInd = 0 } else { @@ -1789,6 +1791,45 @@ func assertHistogram(t *testing.T, name string, histogram dto.Histogram, expecte assert.Equal(t, expectedSum, histogram.GetSampleSum(), name+":sum") } +func TestRecordAdapterBuyerUIDScrubbed(t *testing.T) { + + tests := []struct { + name string + disabled bool + expectedCount float64 + }{ + { + name: "enabled", + disabled: false, + expectedCount: 1, + }, + { + name: "disabled", + disabled: true, + expectedCount: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := createMetricsForTesting() + m.metricsDisabled.AdapterBuyerUIDScrubbed = tt.disabled + adapterName := openrtb_ext.BidderName("AnyName") + lowerCasedAdapterName := "anyname" + m.RecordAdapterBuyerUIDScrubbed(adapterName) + + assertCounterVecValue(t, + "Increment adapter buyeruid scrubbed counter", + "adapter_buyeruids_scrubbed", + m.adapterScrubbedBuyerUIDs, + tt.expectedCount, + prometheus.Labels{ + adapterLabel: lowerCasedAdapterName, + }) + }) + } +} + func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { m := createMetricsForTesting() adapterName := openrtb_ext.BidderName("AnyName") diff --git a/modules/builder.go b/modules/builder.go index ffb814e6407..2101b340231 100644 --- a/modules/builder.go +++ b/modules/builder.go @@ -1,13 +1,17 @@ package modules import ( - prebidOrtb2blocking "github.com/prebid/prebid-server/modules/prebid/ortb2blocking" + fiftyonedegreesDevicedetection "github.com/prebid/prebid-server/v3/modules/fiftyonedegrees/devicedetection" + prebidOrtb2blocking "github.com/prebid/prebid-server/v3/modules/prebid/ortb2blocking" ) // builders returns mapping between module name and its builder // vendor and module names are chosen based on the module directory name func builders() ModuleBuilders { return ModuleBuilders{ + "fiftyonedegrees": { + "devicedetection": fiftyonedegreesDevicedetection.Builder, + }, "prebid": { "ortb2blocking": prebidOrtb2blocking.Builder, }, diff --git a/modules/fiftyonedegrees/devicedetection/README.md b/modules/fiftyonedegrees/devicedetection/README.md new file mode 100644 index 00000000000..645fb407fe5 --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/README.md @@ -0,0 +1,255 @@ +## Overview + +The 51Degrees module enriches an incoming OpenRTB request with [51Degrees Device Data](https://51degrees.com/documentation/_device_detection__overview.html). + +The module sets the following fields of the device object: `make`, `model`, `os`, `osv`, `h`, `w`, `ppi`, `pxratio` - interested bidder adapters may use these fields as needed. In addition the module sets `device.ext.fiftyonedegrees_deviceId` to a permanent device ID which can be rapidly looked up in on premise data exposing over 250 properties including the device age, chip set, codec support, and price, operating system and app/browser versions, age, and embedded features. + +## Operation Details + +### Evidence + +The module uses `device.ua` (User Agent) and `device.sua` (Structured User Agent) provided in the oRTB request payload as input (or 'evidence' in 51Degrees terminology). There is a fallback to the corresponding HTTP request headers if any of these are not present in the oRTB payload - in particular: `User-Agent` and `Sec-CH-UA-*` (aka User-Agent Client Hints). To make sure Prebid.js sends Structured User Agent in the oRTB payload - we strongly advice publishers to enable [First Party Data Enrichment module](dev-docs/modules/enrichmentFpdModule.html) for their wrappers and specify + +```js +pbjs.setConfig({ + firstPartyData: { + uaHints: [ + 'architecture', + 'model', + 'platform', + 'platformVersion', + 'fullVersionList', + ] + } +}) +``` + +### Data File Updates + +The module operates **fully autonomously and does not make any requests to any cloud services in real time to do device detection**. This is an [on-premise data](https://51degrees.com/developers/deployment-options/on-premise-data) deployment in 51Degrees terminology. The module operates using a local data file that is loaded into memory fully or partially during operation. The data file is occasionally updated to accomodate new devices, so it is recommended to enable automatic data updates in the module configuration. Alternatively `watch_file_system` option can be used and the file may be downloaded and replaced on disk manually. See the configuration options below. + +## Setup + +The 51Degrees module operates using a data file. You can get started with a free Lite data file that can be downloaded here: [51Degrees-LiteV4.1.hash](https://github.com/51Degrees/device-detection-data/blob/main/51Degrees-LiteV4.1.hash). The Lite file is capable of detecting limited device information, so if you need in-depth device data, please contact 51Degrees to obtain a license: [https://51degrees.com/contact-us](https://51degrees.com/contact-us?ContactReason=Free%20Trial). + +Put the data file in a file system location writable by the system account that is running the Prebid Server module and specify that directory location in the configuration parameters. The location needs to be writable if you would like to enable [automatic data file updates](https://51degrees.com/documentation/_features__automatic_datafile_updates.html). + +### Execution Plan + +This module supports running at two stages: + +* entrypoint: this is where incoming requests are parsed and device detection evidences are extracted. +* raw-auction-request: this is where outgoing auction requests to each bidder are enriched with the device detection data + +We recommend defining the execution plan right in the account config +so the module is only invoked for specific accounts. See below for an example. + +### Global Config + +There is no host-company level config for this module. + +### Account-Level Config + +To start using current module in PBS-Go you have to enable module and add `fiftyone-devicedetection-entrypoint-hook` and `fiftyone-devicedetection-raw-auction-request-hook` into hooks execution plan inside your config file: +Here's a general template for the account config used in PBS-Go: + +```json +{ + "hooks": { + "enabled":true, + "modules": { + "fiftyonedegrees": { + "devicedetection": { + "enabled": true, + "make_temp_copy": true, + "data_file": { + "path": "path/to/51Degrees-LiteV4.1.hash", + "update": { + "auto": true, + "url": "", + "polling_interval": 1800, + "license_key": "", + "product": "V4Enterprise", + "watch_file_system": "true", + "on_startup": true + } + } + } + }, + "host_execution_plan": { + "endpoints": { + "/openrtb2/auction": { + "stages": { + "entrypoint": { + "groups": [ + { + "timeout": 10, + "hook_sequence": [ + { + "module_code": "fiftyonedegrees.devicedetection", + "hook_impl_code": "fiftyone-devicedetection-entrypoint-hook" + } + ] + } + ] + }, + "raw_auction_request": { + "groups": [ + { + "timeout": 10, + "hook_sequence": [ + { + "module_code": "fiftyonedegrees.devicedetection", + "hook_impl_code": "fiftyone-devicedetection-raw-auction-request-hook" + } + ] + } + ] + } + } + } + } + } + } + } +} +``` + +The same config in YAML format: +```yaml +hooks: + enabled: true + modules: + fiftyonedegrees: + devicedetection: + enabled: true + make_temp_copy: true + data_file: + path: path/to/51Degrees-LiteV4.1.hash + update: + auto: true + url: "" + polling_interval: 1800 + license_key: "" + product: V4Enterprise + watch_file_system: 'true' + host_execution_plan: + endpoints: + "/openrtb2/auction": + stages: + entrypoint: + groups: + - timeout: 10 + hook_sequence: + - module_code: fiftyonedegrees.devicedetection + hook_impl_code: fiftyone-devicedetection-entrypoint-hook + raw_auction_request: + groups: + - timeout: 10 + hook_sequence: + - module_code: fiftyonedegrees.devicedetection + hook_impl_code: fiftyone-devicedetection-raw-auction-request-hook +``` + +Note that at a minimum (besides adding to the host_execution_plan) you need to enable the module and specify a path to the data file in the configuration. +Sample module enablement configuration in JSON and YAML formats: + +```json +{ + "modules": { + "fiftyonedegrees": { + "devicedetection": { + "enabled": true, + "data_file": { + "path": "path/to/51Degrees-LiteV4.1.hash" + } + } + } + } +} +``` + +```yaml + modules: + fiftyonedegrees: + devicedetection: + enabled: true + data_file: + path: "/path/to/51Degrees-LiteV4.1.hash" +``` + +## Module Configuration Parameters + +The parameter names are specified with full path using dot-notation. F.e. `section_name` .`sub_section` .`param_name` would result in this nesting in the JSON configuration: + +```json +{ + "section_name": { + "sub_section": { + "param_name": "param-value" + } + } +} +``` + +| Param Name | Required| Type | Default value | Description | +|:-------|:------|:------|:------|:---------------------------------------| +| `account_filter` .`allow_list` | No | list of strings | [] (empty list) | A list of account IDs that are allowed to use this module - only relevant if enabled globally for the host. If empty, all accounts are allowed. Full-string match is performed (whitespaces and capitalization matter). | +| `data_file` .`path` | **Yes** | string | null |The full path to the device detection data file. Sample file can be downloaded from [data repo on GitHub](https://github.com/51Degrees/device-detection-data/blob/main/51Degrees-LiteV4.1.hash), or get an Enterprise data file [here](https://51degrees.com/pricing). | +| `data_file` .`make_temp_copy` | No | boolean | true | If true, the engine will create a temporary copy of the data file rather than using the data file directly. | +| `data_file` .`update` .`auto` | No | boolean | true | If enabled, the engine will periodically (at predefined time intervals - see `polling-interval` parameter) check if new data file is available. When the new data file is available engine downloads it and switches to it for device detection. If custom `url` is not specified `license_key` param is required. | +| `data_file` .`update` .`on_startup` | No | boolean | false | If enabled, engine will check for the updated data file right away without waiting for the defined time interval. | +| `data_file` .`update` .`url` | No | string | null | Configure the engine to check the specified URL for the availability of the updated data file. If not specified the [51Degrees distributor service](https://51degrees.com/documentation/4.4/_info__distributor.html) URL will be used, which requires a License Key. | +| `data_file` .`update` .`license_key` | No | string | null | Required if `auto` is true and custom `url` is not specified. Allows to download the data file from the [51Degrees distributor service](https://51degrees.com/documentation/4.4/_info__distributor.html). | +| `data_file` .`update` .`watch_file_system` | No | boolean | true | If enabled the engine will watch the data file path for any changes, and automatically reload the data file from disk once it is updated. | +| `data_file` .`update` .`polling_interval` | No | int | 1800 | The time interval in seconds between consequent attempts to download an updated data file. Default = 1800 seconds = 30 minutes. | +| `data_file` .`update` .`product`| No | string | `V4Enterprise` | Set the Product used when checking for new device detection data files. A Product is exclusive to the 51Degrees paid service. Please see options [here](https://51degrees.com/documentation/_info__distributor.html). | +| `performance` .`profile` | No | string | `Balanced` | `performance.*` parameters are related to the tradeoffs between speed of device detection and RAM consumption or accuracy. `profile` dictates the proportion between the use of the RAM (the more RAM used - the faster is the device detection) and reads from disk (less RAM but slower device detection). Must be one of: `LowMemory`, `MaxPerformance`, `HighPerformance`, `Balanced`, `BalancedTemp`, `InMemory`. Defaults to `Balanced`. | +| `performance` .`concurrency` | No | int | 10 | Specify the expected number of concurrent operations that engine does. This sets the concurrency of the internal caches to avoid excessive locking. Default: 10. | +| `performance` .`difference` | No | int | 0 | Set the maximum difference to allow when processing evidence (HTTP headers). The meaning is the difference in hash value between the hash that was found, and the hash that is being searched for. By default this is 0. For more information see [51Degrees documentation](https://51degrees.com/documentation/_device_detection__hash.html). | +| `performance` .`drift` | No | int | 0 | Set the maximum drift to allow when matching hashes. If the drift is exceeded, the result is considered invalid and values will not be returned. By default this is 0. For more information see [51Degrees documentation](https://51degrees.com/documentation/_device_detection__hash.html). | +| `performance` .`allow_unmatched` | No | boolean | false | If set to false, a non-matching evidence will result in properties with no values set. If set to true, a non-matching evidence will cause the 'default profiles' to be returned. This means that properties will always have values (i.e. no need to check .hasValue) but some may be inaccurate. By default, this is false. | + +## Running the demo + +1. Download dependencies: +```bash +go mod download +``` + +2. Replace the original config file `pbs.json` (placed in the repository root or in `/etc/config`) with the sample [config file](sample/pbs.json): +``` +cp modules/fiftyonedegrees/devicedetection/sample/pbs.json pbs.json +``` + +3. Download `51Degrees-LiteV4.1.hash` from [[GitHub](https://github.com/51Degrees/device-detection-data/blob/main/51Degrees-LiteV4.1.hash)] and put it in the project root directory. + +```bash +curl -o 51Degrees-LiteV4.1.hash -L https://github.com/51Degrees/device-detection-data/raw/main/51Degrees-LiteV4.1.hash +``` + +4. Create a directory for sample stored requests (needed for the server to run): +```bash +mkdir -p sample/stored +``` + +5. Start the server: +```bash +go run main.go +``` + +6. Run sample request: +```bash +curl \ +--header "Content-Type: application/json" \ +http://localhost:8000/openrtb2/auction \ +--data @modules/fiftyonedegrees/devicedetection/sample/request_data.json +``` + +7. Observe the `device` object get enriched with `devicetype`, `os`, `osv`, `w`, `h` and `ext.fiftyonedegrees_deviceId`. + +## Maintainer contacts + +Any suggestions or questions can be directed to [support@51degrees.com](support@51degrees.com) e-mail. + +Or just open new [issue](https://github.com/prebid/prebid-server/issues/new) or [pull request](https://github.com/prebid/prebid-server/pulls) in this repository. diff --git a/modules/fiftyonedegrees/devicedetection/account_info_extractor.go b/modules/fiftyonedegrees/devicedetection/account_info_extractor.go new file mode 100644 index 00000000000..2a5168cfe0c --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/account_info_extractor.go @@ -0,0 +1,37 @@ +package devicedetection + +import ( + "github.com/tidwall/gjson" +) + +type accountInfo struct { + Id string +} + +type accountInfoExtractor struct{} + +func newAccountInfoExtractor() accountInfoExtractor { + return accountInfoExtractor{} +} + +// extract extracts the account information from the payload +// The account information is extracted from the publisher id or site publisher id +func (x accountInfoExtractor) extract(payload []byte) *accountInfo { + if payload == nil { + return nil + } + + publisherResult := gjson.GetBytes(payload, "app.publisher.id") + if publisherResult.Exists() { + return &accountInfo{ + Id: publisherResult.String(), + } + } + publisherResult = gjson.GetBytes(payload, "site.publisher.id") + if publisherResult.Exists() { + return &accountInfo{ + Id: publisherResult.String(), + } + } + return nil +} diff --git a/modules/fiftyonedegrees/devicedetection/account_info_extractor_test.go b/modules/fiftyonedegrees/devicedetection/account_info_extractor_test.go new file mode 100644 index 00000000000..2d32f7915b5 --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/account_info_extractor_test.go @@ -0,0 +1,74 @@ +package devicedetection + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + siteRequestPayload = []byte(` + { + "site": { + "publisher": { + "id": "p-bid-config-test-005" + } + } + } + `) + + mobileRequestPayload = []byte(` + { + "app": { + "publisher": { + "id": "p-bid-config-test-005" + } + } + } + `) + + emptyPayload = []byte(`{}`) +) + +func TestPublisherIdExtraction(t *testing.T) { + tests := []struct { + name string + payload []byte + expected string + expectNil bool + }{ + { + name: "SiteRequest", + payload: siteRequestPayload, + expected: "p-bid-config-test-005", + }, + { + name: "MobileRequest", + payload: mobileRequestPayload, + expected: "p-bid-config-test-005", + }, + { + name: "EmptyPublisherId", + payload: emptyPayload, + expectNil: true, + }, + { + name: "EmptyPayload", + payload: nil, + expectNil: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + extractor := newAccountInfoExtractor() + accountInfo := extractor.extract(tt.payload) + + if tt.expectNil { + assert.Nil(t, accountInfo) + } else { + assert.Equal(t, tt.expected, accountInfo.Id) + } + }) + } +} diff --git a/modules/fiftyonedegrees/devicedetection/account_validator.go b/modules/fiftyonedegrees/devicedetection/account_validator.go new file mode 100644 index 00000000000..fdff92531a7 --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/account_validator.go @@ -0,0 +1,28 @@ +package devicedetection + +import "slices" + +// defaultAccountValidator is a struct that contains an accountInfoExtractor +// and is used to validate if an account is allowed +type defaultAccountValidator struct { + AccountExtractor accountInfoExtractor +} + +func newAccountValidator() *defaultAccountValidator { + return &defaultAccountValidator{ + AccountExtractor: newAccountInfoExtractor(), + } +} + +func (x defaultAccountValidator) isAllowed(cfg config, req []byte) bool { + if len(cfg.AccountFilter.AllowList) == 0 { + return true + } + + accountInfo := x.AccountExtractor.extract(req) + if accountInfo != nil && slices.Contains(cfg.AccountFilter.AllowList, accountInfo.Id) { + return true + } + + return false +} diff --git a/modules/fiftyonedegrees/devicedetection/account_validator_test.go b/modules/fiftyonedegrees/devicedetection/account_validator_test.go new file mode 100644 index 00000000000..25f99e3b796 --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/account_validator_test.go @@ -0,0 +1,71 @@ +package devicedetection + +import ( + "encoding/json" + "testing" + + "github.com/prebid/openrtb/v20/openrtb2" + + "github.com/stretchr/testify/assert" +) + +func TestIsAllowed(t *testing.T) { + tests := []struct { + name string + allowList []string + expectedResult bool + }{ + { + name: "allowed", + allowList: []string{"1001"}, + expectedResult: true, + }, + { + name: "empty", + allowList: []string{}, + expectedResult: true, + }, + { + name: "disallowed", + allowList: []string{"1002"}, + expectedResult: false, + }, + { + name: "allow_list_is_nil", + allowList: nil, + expectedResult: true, + }, + { + name: "allow_list_contains_multiple", + allowList: []string{"1000", "1001", "1002"}, + expectedResult: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + validator := newAccountValidator() + cfg := config{ + AccountFilter: accountFilter{AllowList: test.allowList}, + } + + res := validator.isAllowed( + cfg, toBytes( + &openrtb2.BidRequest{ + App: &openrtb2.App{ + Publisher: &openrtb2.Publisher{ + ID: "1001", + }, + }, + }, + ), + ) + assert.Equal(t, test.expectedResult, res) + }) + } +} + +func toBytes(v interface{}) []byte { + res, _ := json.Marshal(v) + return res +} diff --git a/modules/fiftyonedegrees/devicedetection/config.go b/modules/fiftyonedegrees/devicedetection/config.go new file mode 100644 index 00000000000..a5c302bcff5 --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/config.go @@ -0,0 +1,80 @@ +package devicedetection + +import ( + "encoding/json" + "os" + + "github.com/51Degrees/device-detection-go/v4/dd" + "github.com/pkg/errors" + + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +type config struct { + DataFile dataFile `json:"data_file"` + AccountFilter accountFilter `json:"account_filter"` + Performance performance `json:"performance"` +} + +type dataFile struct { + Path string `json:"path"` + Update dataFileUpdate `json:"update"` + MakeTempCopy *bool `json:"make_temp_copy"` +} + +type dataFileUpdate struct { + Auto bool `json:"auto"` + Url string `json:"url"` + License string `json:"license_key"` + PollingInterval int `json:"polling_interval"` + Product string `json:"product"` + WatchFileSystem *bool `json:"watch_file_system"` + OnStartup bool `json:"on_startup"` +} + +type accountFilter struct { + AllowList []string `json:"allow_list"` +} + +type performance struct { + Profile string `json:"profile"` + Concurrency *int `json:"concurrency"` + Difference *int `json:"difference"` + AllowUnmatched *bool `json:"allow_unmatched"` + Drift *int `json:"drift"` +} + +var performanceProfileMap = map[string]dd.PerformanceProfile{ + "Default": dd.Default, + "LowMemory": dd.LowMemory, + "BalancedTemp": dd.BalancedTemp, + "Balanced": dd.Balanced, + "HighPerformance": dd.HighPerformance, + "InMemory": dd.InMemory, +} + +func (c *config) getPerformanceProfile() dd.PerformanceProfile { + mappedResult, ok := performanceProfileMap[c.Performance.Profile] + if !ok { + return dd.Default + } + + return mappedResult +} + +func parseConfig(data json.RawMessage) (config, error) { + var cfg config + if err := jsonutil.UnmarshalValid(data, &cfg); err != nil { + return cfg, errors.Wrap(err, "failed to parse config") + } + return cfg, nil +} + +func validateConfig(cfg config) error { + _, err := os.Stat(cfg.DataFile.Path) + if err != nil { + return errors.Wrap(err, "error opening hash file path") + } + + return nil +} diff --git a/modules/fiftyonedegrees/devicedetection/config_test.go b/modules/fiftyonedegrees/devicedetection/config_test.go new file mode 100644 index 00000000000..e2478d82b7d --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/config_test.go @@ -0,0 +1,119 @@ +package devicedetection + +import ( + "os" + "testing" + + "github.com/51Degrees/device-detection-go/v4/dd" + "github.com/stretchr/testify/assert" +) + +func TestParseConfig(t *testing.T) { + cfgRaw := []byte(`{ + "enabled": true, + "data_file": { + "path": "path/to/51Degrees-LiteV4.1.hash", + "update": { + "auto": true, + "url": "https://my.datafile.com/datafile.gz", + "polling_interval": 3600, + "license_key": "your_license_key", + "product": "V4Enterprise", + "on_startup": true + } + }, + "account_filter": {"allow_list": ["123"]}, + "performance": { + "profile": "default", + "concurrency": 1, + "difference": 1, + "allow_unmatched": true, + "drift": 1 + } + }`) + + cfg, err := parseConfig(cfgRaw) + + assert.NoError(t, err) + + assert.Equal(t, cfg.DataFile.Path, "path/to/51Degrees-LiteV4.1.hash") + assert.True(t, cfg.DataFile.Update.Auto) + assert.Equal(t, cfg.DataFile.Update.Url, "https://my.datafile.com/datafile.gz") + assert.Equal(t, cfg.DataFile.Update.PollingInterval, 3600) + assert.Equal(t, cfg.DataFile.Update.License, "your_license_key") + assert.Equal(t, cfg.DataFile.Update.Product, "V4Enterprise") + assert.True(t, cfg.DataFile.Update.OnStartup) + assert.Equal(t, cfg.AccountFilter.AllowList, []string{"123"}) + assert.Equal(t, cfg.Performance.Profile, "default") + assert.Equal(t, *cfg.Performance.Concurrency, 1) + assert.Equal(t, *cfg.Performance.Difference, 1) + assert.True(t, *cfg.Performance.AllowUnmatched) + assert.Equal(t, *cfg.Performance.Drift, 1) + assert.Equal(t, cfg.getPerformanceProfile(), dd.Default) +} + +func TestValidateConfig(t *testing.T) { + file, err := os.Create("test-validate-config.hash") + if err != nil { + t.Errorf("Failed to create file: %v", err) + } + defer file.Close() + defer os.Remove("test-validate-config.hash") + + cfgRaw := []byte(`{ + "enabled": true, + "data_file": { + "path": "test-validate-config.hash", + "update": { + "auto": true, + "url": "https://my.datafile.com/datafile.gz", + "polling_interval": 3600, + "licence_key": "your_licence_key", + "product": "V4Enterprise" + } + }, + "account_filter": {"allow_list": ["123"]}, + "performance": { + "profile": "default", + "concurrency": 1, + "difference": 1, + "allow_unmatched": true, + "drift": 1 + } + }`) + + cfg, err := parseConfig(cfgRaw) + assert.NoError(t, err) + + err = validateConfig(cfg) + assert.NoError(t, err) + +} + +func TestInvalidPerformanceProfile(t *testing.T) { + cfgRaw := []byte(`{ + "enabled": true, + "data_file": { + "path": "test-validate-config.hash", + "update": { + "auto": true, + "url": "https://my.datafile.com/datafile.gz", + "polling_interval": 3600, + "licence_key": "your_licence_key", + "product": "V4Enterprise" + } + }, + "account_filter": {"allow_list": ["123"]}, + "performance": { + "profile": "123", + "concurrency": 1, + "difference": 1, + "allow_unmatched": true, + "drift": 1 + } + }`) + cfg, err := parseConfig(cfgRaw) + assert.NoError(t, err) + + assert.Equal(t, cfg.getPerformanceProfile(), dd.Default) +} diff --git a/modules/fiftyonedegrees/devicedetection/context.go b/modules/fiftyonedegrees/devicedetection/context.go new file mode 100644 index 00000000000..3c10dd2f393 --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/context.go @@ -0,0 +1,8 @@ +package devicedetection + +// Context keys for device detection +const ( + evidenceFromHeadersCtxKey = "evidence_from_headers" + evidenceFromSuaCtxKey = "evidence_from_sua" + ddEnabledCtxKey = "dd_enabled" +) diff --git a/modules/fiftyonedegrees/devicedetection/device_detector.go b/modules/fiftyonedegrees/devicedetection/device_detector.go new file mode 100644 index 00000000000..8369d343d34 --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/device_detector.go @@ -0,0 +1,157 @@ +package devicedetection + +import ( + "github.com/51Degrees/device-detection-go/v4/dd" + "github.com/51Degrees/device-detection-go/v4/onpremise" + "github.com/pkg/errors" +) + +type engine interface { + Process(evidences []onpremise.Evidence) (*dd.ResultsHash, error) + GetHttpHeaderKeys() []dd.EvidenceKey +} + +type extractor interface { + extract(results Results, ua string) (*deviceInfo, error) +} + +type defaultDeviceDetector struct { + cfg *dd.ConfigHash + deviceInfoExtractor extractor + engine engine +} + +func newDeviceDetector(cfg *dd.ConfigHash, moduleConfig *config) (*defaultDeviceDetector, error) { + engineOptions := buildEngineOptions(moduleConfig, cfg) + + ddEngine, err := onpremise.New( + engineOptions..., + ) + if err != nil { + return nil, errors.Wrap(err, "Failed to create onpremise engine.") + } + + deviceDetector := &defaultDeviceDetector{ + engine: ddEngine, + cfg: cfg, + deviceInfoExtractor: newDeviceInfoExtractor(), + } + + return deviceDetector, nil +} + +func buildEngineOptions(moduleConfig *config, configHash *dd.ConfigHash) []onpremise.EngineOptions { + options := []onpremise.EngineOptions{ + onpremise.WithDataFile(moduleConfig.DataFile.Path), + } + + options = append( + options, + onpremise.WithProperties([]string{ + "HardwareVendor", + "HardwareName", + "DeviceType", + "PlatformVendor", + "PlatformName", + "PlatformVersion", + "BrowserVendor", + "BrowserName", + "BrowserVersion", + "ScreenPixelsWidth", + "ScreenPixelsHeight", + "PixelRatio", + "Javascript", + "GeoLocation", + "HardwareModel", + "HardwareFamily", + "HardwareModelVariants", + "ScreenInchesHeight", + "IsCrawler", + }), + ) + + options = append( + options, + onpremise.WithConfigHash(configHash), + ) + + if moduleConfig.DataFile.MakeTempCopy != nil { + options = append( + options, + onpremise.WithTempDataCopy(*moduleConfig.DataFile.MakeTempCopy), + ) + } + + dataUpdateOptions := []onpremise.EngineOptions{ + onpremise.WithAutoUpdate(moduleConfig.DataFile.Update.Auto), + } + + if moduleConfig.DataFile.Update.Url != "" { + dataUpdateOptions = append( + dataUpdateOptions, + onpremise.WithDataUpdateUrl( + moduleConfig.DataFile.Update.Url, + ), + ) + } + + if moduleConfig.DataFile.Update.PollingInterval > 0 { + dataUpdateOptions = append( + dataUpdateOptions, + onpremise.WithPollingInterval( + moduleConfig.DataFile.Update.PollingInterval, + ), + ) + } + + if moduleConfig.DataFile.Update.License != "" { + dataUpdateOptions = append( + dataUpdateOptions, + onpremise.WithLicenseKey(moduleConfig.DataFile.Update.License), + ) + } + + if moduleConfig.DataFile.Update.Product != "" { + dataUpdateOptions = append( + dataUpdateOptions, + onpremise.WithProduct(moduleConfig.DataFile.Update.Product), + ) + } + + if moduleConfig.DataFile.Update.WatchFileSystem != nil { + dataUpdateOptions = append( + dataUpdateOptions, + onpremise.WithFileWatch( + *moduleConfig.DataFile.Update.WatchFileSystem, + ), + ) + } + + dataUpdateOptions = append( + dataUpdateOptions, + onpremise.WithUpdateOnStart(moduleConfig.DataFile.Update.OnStartup), + ) + + options = append( + options, + dataUpdateOptions..., + ) + + return options +} + +func (x defaultDeviceDetector) getSupportedHeaders() []dd.EvidenceKey { + return x.engine.GetHttpHeaderKeys() +} + +func (x defaultDeviceDetector) getDeviceInfo(evidence []onpremise.Evidence, ua string) (*deviceInfo, error) { + results, err := x.engine.Process(evidence) + if err != nil { + return nil, errors.Wrap(err, "Failed to process evidence") + } + defer results.Free() + + deviceInfo, err := x.deviceInfoExtractor.extract(results, ua) + + return deviceInfo, err +} diff --git a/modules/fiftyonedegrees/devicedetection/device_detector_test.go b/modules/fiftyonedegrees/devicedetection/device_detector_test.go new file mode 100644 index 00000000000..84d6ab28cc0 --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/device_detector_test.go @@ -0,0 +1,190 @@ +package devicedetection + +import ( + "fmt" + "testing" + + "github.com/51Degrees/device-detection-go/v4/dd" + "github.com/51Degrees/device-detection-go/v4/onpremise" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestBuildEngineOptions(t *testing.T) { + cases := []struct { + cfgRaw []byte + length int + }{ + { + cfgRaw: []byte(`{ + "enabled": true, + "data_file": { + "path": "path/to/51Degrees-LiteV4.1.hash", + "update": { + "auto": true, + "url": "https://my.datafile.com/datafile.gz", + "polling_interval": 3600, + "license_key": "your_license_key", + "product": "V4Enterprise", + "watch_file_system": true, + "on_startup": true + }, + "make_temp_copy": true + }, + "account_filter": {"allow_list": ["123"]}, + "performance": { + "profile": "default", + "concurrency": 1, + "difference": 1, + "allow_unmatched": true, + "drift": 1 + } + }`), + length: 11, + // data_file.path, data_file.update.auto:true, url, polling_interval, license_key, product, confighash, properties + // data_file.update.on_startup:true, data_file.update.watch_file_system:true, data_file.make_temp_copy:true + }, + { + cfgRaw: []byte(`{ + "enabled": true, + "data_file": { + "path": "path/to/51Degrees-LiteV4.1.hash" + }, + "account_filter": {"allow_list": ["123"]}, + "performance": { + "profile": "default", + "concurrency": 1, + "difference": 1, + "allow_unmatched": true, + "drift": 1 + } + }`), + length: 5, // data_file.update.auto:false, data_file.path, confighash, properties, data_file.update.on_startup:false + }, + } + + for _, c := range cases { + cfg, err := parseConfig(c.cfgRaw) + assert.NoError(t, err) + configHash := configHashFromConfig(&cfg) + options := buildEngineOptions(&cfg, configHash) + assert.Equal(t, c.length, len(options)) + } +} + +type engineMock struct { + mock.Mock +} + +func (e *engineMock) Process(evidences []onpremise.Evidence) (*dd.ResultsHash, error) { + args := e.Called(evidences) + res := args.Get(0) + if res == nil { + return nil, args.Error(1) + } + + return res.(*dd.ResultsHash), args.Error(1) +} + +func (e *engineMock) GetHttpHeaderKeys() []dd.EvidenceKey { + args := e.Called() + return args.Get(0).([]dd.EvidenceKey) +} + +type extractorMock struct { + mock.Mock +} + +func (e *extractorMock) extract(results Results, ua string) (*deviceInfo, error) { + args := e.Called(results, ua) + return args.Get(0).(*deviceInfo), args.Error(1) +} + +func TestGetDeviceInfo(t *testing.T) { + tests := []struct { + name string + engineResponse *dd.ResultsHash + engineError error + expectedResult *deviceInfo + expectedError string + }{ + { + name: "Success_path", + engineResponse: &dd.ResultsHash{}, + engineError: nil, + expectedResult: &deviceInfo{ + DeviceId: "123", + }, + expectedError: "", + }, + { + name: "Error_path", + engineResponse: nil, + engineError: fmt.Errorf("error"), + expectedResult: nil, + expectedError: "Failed to process evidence: error", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + extractorM := &extractorMock{} + extractorM.On("extract", mock.Anything, mock.Anything).Return( + &deviceInfo{ + DeviceId: "123", + }, nil, + ) + + engineM := &engineMock{} + engineM.On("Process", mock.Anything).Return( + tt.engineResponse, tt.engineError, + ) + + deviceDetector := defaultDeviceDetector{ + cfg: nil, + deviceInfoExtractor: extractorM, + engine: engineM, + } + + result, err := deviceDetector.getDeviceInfo( + []onpremise.Evidence{{ + Prefix: dd.HttpEvidenceQuery, + Key: "key", + Value: "val", + }}, "ua", + ) + + if tt.expectedError == "" { + assert.NoError(t, err) + assert.NotNil(t, result) + assert.Equal(t, tt.expectedResult.DeviceId, result.DeviceId) + } else { + assert.Errorf(t, err, tt.expectedError) + assert.Nil(t, result) + } + }) + } +} + +func TestGetSupportedHeaders(t *testing.T) { + engineM := &engineMock{} + + engineM.On("GetHttpHeaderKeys").Return( + []dd.EvidenceKey{{ + Key: "key", + Prefix: dd.HttpEvidenceQuery, + }}, + ) + + deviceDetector := defaultDeviceDetector{ + cfg: nil, + deviceInfoExtractor: nil, + engine: engineM, + } + + result := deviceDetector.getSupportedHeaders() + assert.NotNil(t, result) + assert.Equal(t, len(result), 1) + assert.Equal(t, result[0].Key, "key") + +} diff --git a/modules/fiftyonedegrees/devicedetection/device_info_extractor.go b/modules/fiftyonedegrees/devicedetection/device_info_extractor.go new file mode 100644 index 00000000000..1c913e21696 --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/device_info_extractor.go @@ -0,0 +1,121 @@ +package devicedetection + +import ( + "strconv" + + "github.com/golang/glog" + "github.com/pkg/errors" +) + +// deviceInfoExtractor is a struct that contains the methods to extract device information +// from the results of the device detection +type deviceInfoExtractor struct{} + +func newDeviceInfoExtractor() deviceInfoExtractor { + return deviceInfoExtractor{} +} + +type Results interface { + ValuesString(string, string) (string, error) + HasValues(string) (bool, error) + DeviceId() (string, error) +} + +type deviceInfoProperty string + +const ( + deviceInfoHardwareVendor deviceInfoProperty = "HardwareVendor" + deviceInfoHardwareName deviceInfoProperty = "HardwareName" + deviceInfoDeviceType deviceInfoProperty = "DeviceType" + deviceInfoPlatformVendor deviceInfoProperty = "PlatformVendor" + deviceInfoPlatformName deviceInfoProperty = "PlatformName" + deviceInfoPlatformVersion deviceInfoProperty = "PlatformVersion" + deviceInfoBrowserVendor deviceInfoProperty = "BrowserVendor" + deviceInfoBrowserName deviceInfoProperty = "BrowserName" + deviceInfoBrowserVersion deviceInfoProperty = "BrowserVersion" + deviceInfoScreenPixelsWidth deviceInfoProperty = "ScreenPixelsWidth" + deviceInfoScreenPixelsHeight deviceInfoProperty = "ScreenPixelsHeight" + deviceInfoPixelRatio deviceInfoProperty = "PixelRatio" + deviceInfoJavascript deviceInfoProperty = "Javascript" + deviceInfoGeoLocation deviceInfoProperty = "GeoLocation" + deviceInfoHardwareModel deviceInfoProperty = "HardwareModel" + deviceInfoHardwareFamily deviceInfoProperty = "HardwareFamily" + deviceInfoHardwareModelVariants deviceInfoProperty = "HardwareModelVariants" + deviceInfoScreenInchesHeight deviceInfoProperty = "ScreenInchesHeight" +) + +func (x deviceInfoExtractor) extract(results Results, ua string) (*deviceInfo, error) { + hardwareVendor := x.getValue(results, deviceInfoHardwareVendor) + hardwareName := x.getValue(results, deviceInfoHardwareName) + deviceType := x.getValue(results, deviceInfoDeviceType) + platformVendor := x.getValue(results, deviceInfoPlatformVendor) + platformName := x.getValue(results, deviceInfoPlatformName) + platformVersion := x.getValue(results, deviceInfoPlatformVersion) + browserVendor := x.getValue(results, deviceInfoBrowserVendor) + browserName := x.getValue(results, deviceInfoBrowserName) + browserVersion := x.getValue(results, deviceInfoBrowserVersion) + screenPixelsWidth, _ := strconv.ParseInt(x.getValue(results, deviceInfoScreenPixelsWidth), 10, 64) + screenPixelsHeight, _ := strconv.ParseInt(x.getValue(results, deviceInfoScreenPixelsHeight), 10, 64) + pixelRatio, _ := strconv.ParseFloat(x.getValue(results, deviceInfoPixelRatio), 10) + javascript, _ := strconv.ParseBool(x.getValue(results, deviceInfoJavascript)) + geoLocation, _ := strconv.ParseBool(x.getValue(results, deviceInfoGeoLocation)) + deviceId, err := results.DeviceId() + if err != nil { + return nil, errors.Wrap(err, "Failed to get device id.") + } + hardwareModel := x.getValue(results, deviceInfoHardwareModel) + hardwareFamily := x.getValue(results, deviceInfoHardwareFamily) + hardwareModelVariants := x.getValue(results, deviceInfoHardwareModelVariants) + screenInchedHeight, _ := strconv.ParseFloat(x.getValue(results, deviceInfoScreenInchesHeight), 10) + + p := &deviceInfo{ + HardwareVendor: hardwareVendor, + HardwareName: hardwareName, + DeviceType: deviceType, + PlatformVendor: platformVendor, + PlatformName: platformName, + PlatformVersion: platformVersion, + BrowserVendor: browserVendor, + BrowserName: browserName, + BrowserVersion: browserVersion, + ScreenPixelsWidth: screenPixelsWidth, + ScreenPixelsHeight: screenPixelsHeight, + PixelRatio: pixelRatio, + Javascript: javascript, + GeoLocation: geoLocation, + UserAgent: ua, + DeviceId: deviceId, + HardwareModel: hardwareModel, + HardwareFamily: hardwareFamily, + HardwareModelVariants: hardwareModelVariants, + ScreenInchesHeight: screenInchedHeight, + } + + return p, nil +} + +// function getValue return a value results for a property +func (x deviceInfoExtractor) getValue(results Results, propertyName deviceInfoProperty) string { + // Get the values in string + value, err := results.ValuesString( + string(propertyName), + ",", + ) + if err != nil { + glog.Errorf("Failed to get results values string.") + return "" + } + + hasValues, err := results.HasValues(string(propertyName)) + if err != nil { + glog.Errorf("Failed to check if a matched value exists for property %s.\n", propertyName) + return "" + } + + if !hasValues { + glog.Warningf("Property %s does not have a matched value.\n", propertyName) + return "Unknown" + } + + return value +} diff --git a/modules/fiftyonedegrees/devicedetection/device_info_extractor_test.go b/modules/fiftyonedegrees/devicedetection/device_info_extractor_test.go new file mode 100644 index 00000000000..197e3928602 --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/device_info_extractor_test.go @@ -0,0 +1,130 @@ +package devicedetection + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type ResultsHashMock struct { + mock.Mock +} + +func (m *ResultsHashMock) DeviceId() (string, error) { + return "", nil +} + +func (m *ResultsHashMock) ValuesString(prop1 string, prop2 string) (string, error) { + args := m.Called(prop1, prop2) + return args.String(0), args.Error(1) +} + +func (m *ResultsHashMock) HasValues(prop1 string) (bool, error) { + args := m.Called(prop1) + return args.Bool(0), args.Error(1) +} + +func TestDeviceInfoExtraction(t *testing.T) { + results := &ResultsHashMock{} + + extractor := newDeviceInfoExtractor() + mockValue(results, "HardwareName", "Macbook") + mockValues(results) + + deviceInfo, _ := extractor.extract(results, "ua") + assert.NotNil(t, deviceInfo) + + assert.Equal(t, deviceInfo.HardwareName, "Macbook") + assertDeviceInfo(t, deviceInfo) +} + +func TestDeviceInfoExtractionNoProperty(t *testing.T) { + results := &ResultsHashMock{} + + extractor := newDeviceInfoExtractor() + results.Mock.On("ValuesString", "HardwareName", ",").Return("", errors.New("Error")) + results.Mock.On("HasValues", "HardwareName").Return(true, nil) + mockValues(results) + + deviceInfo, _ := extractor.extract(results, "ua") + assert.NotNil(t, deviceInfo) + + assertDeviceInfo(t, deviceInfo) + assert.Equal(t, deviceInfo.HardwareName, "") +} + +func TestDeviceInfoExtractionNoValue(t *testing.T) { + results := &ResultsHashMock{} + + extractor := newDeviceInfoExtractor() + mockValues(results) + mockValue(results, "HardwareVendor", "Apple") + + results.Mock.On("ValuesString", "HardwareName", ",").Return("Macbook", nil) + results.Mock.On("HasValues", "HardwareName").Return(false, nil) + + deviceInfo, _ := extractor.extract(results, "ua") + assert.NotNil(t, deviceInfo) + assertDeviceInfo(t, deviceInfo) + assert.Equal(t, deviceInfo.HardwareName, "Unknown") +} + +func TestDeviceInfoExtractionHasValueError(t *testing.T) { + results := &ResultsHashMock{} + + extractor := newDeviceInfoExtractor() + mockValue(results, "HardwareVendor", "Apple") + + results.Mock.On("ValuesString", "HardwareName", ",").Return("Macbook", nil) + results.Mock.On("HasValues", "HardwareName").Return(true, errors.New("error")) + + mockValues(results) + + deviceInfo, _ := extractor.extract(results, "ua") + assert.NotNil(t, deviceInfo) + assertDeviceInfo(t, deviceInfo) + assert.Equal(t, deviceInfo.HardwareName, "") +} + +func mockValues(results *ResultsHashMock) { + mockValue(results, "HardwareVendor", "Apple") + mockValue(results, "DeviceType", "Desctop") + mockValue(results, "PlatformVendor", "Apple") + mockValue(results, "PlatformName", "MacOs") + mockValue(results, "PlatformVersion", "14") + mockValue(results, "BrowserVendor", "Google") + mockValue(results, "BrowserName", "Crome") + mockValue(results, "BrowserVersion", "12") + mockValue(results, "ScreenPixelsWidth", "1024") + mockValue(results, "ScreenPixelsHeight", "1080") + mockValue(results, "PixelRatio", "223") + mockValue(results, "Javascript", "true") + mockValue(results, "GeoLocation", "true") + mockValue(results, "HardwareModel", "Macbook") + mockValue(results, "HardwareFamily", "Macbook") + mockValue(results, "HardwareModelVariants", "Macbook") + mockValue(results, "ScreenInchesHeight", "12") +} + +func assertDeviceInfo(t *testing.T, deviceInfo *deviceInfo) { + assert.Equal(t, deviceInfo.HardwareVendor, "Apple") + assert.Equal(t, deviceInfo.DeviceType, "Desctop") + assert.Equal(t, deviceInfo.PlatformVendor, "Apple") + assert.Equal(t, deviceInfo.PlatformName, "MacOs") + assert.Equal(t, deviceInfo.PlatformVersion, "14") + assert.Equal(t, deviceInfo.BrowserVendor, "Google") + assert.Equal(t, deviceInfo.BrowserName, "Crome") + assert.Equal(t, deviceInfo.BrowserVersion, "12") + assert.Equal(t, deviceInfo.ScreenPixelsWidth, int64(1024)) + assert.Equal(t, deviceInfo.ScreenPixelsHeight, int64(1080)) + assert.Equal(t, deviceInfo.PixelRatio, float64(223)) + assert.Equal(t, deviceInfo.Javascript, true) + assert.Equal(t, deviceInfo.GeoLocation, true) +} + +func mockValue(results *ResultsHashMock, name string, value string) { + results.Mock.On("ValuesString", name, ",").Return(value, nil) + results.Mock.On("HasValues", name).Return(true, nil) +} diff --git a/modules/fiftyonedegrees/devicedetection/evidence_extractor.go b/modules/fiftyonedegrees/devicedetection/evidence_extractor.go new file mode 100644 index 00000000000..a99a921e75f --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/evidence_extractor.go @@ -0,0 +1,118 @@ +package devicedetection + +import ( + "net/http" + + "github.com/51Degrees/device-detection-go/v4/onpremise" + "github.com/pkg/errors" + + "github.com/51Degrees/device-detection-go/v4/dd" + "github.com/prebid/prebid-server/v3/hooks/hookstage" +) + +type defaultEvidenceExtractor struct { + valFromHeaders evidenceFromRequestHeadersExtractor + valFromSUA evidenceFromSUAPayloadExtractor +} + +func newEvidenceExtractor() *defaultEvidenceExtractor { + evidenceExtractor := &defaultEvidenceExtractor{ + valFromHeaders: newEvidenceFromRequestHeadersExtractor(), + valFromSUA: newEvidenceFromSUAPayloadExtractor(), + } + + return evidenceExtractor +} + +func (x *defaultEvidenceExtractor) fromHeaders(request *http.Request, httpHeaderKeys []dd.EvidenceKey) []stringEvidence { + return x.valFromHeaders.extract(request, httpHeaderKeys) +} + +func (x *defaultEvidenceExtractor) fromSuaPayload(payload []byte) []stringEvidence { + return x.valFromSUA.extract(payload) +} + +// merge merges two slices of stringEvidence into one slice of stringEvidence +func merge(val1, val2 []stringEvidence) []stringEvidence { + evidenceMap := make(map[string]stringEvidence) + for _, e := range val1 { + evidenceMap[e.Key] = e + } + + for _, e := range val2 { + _, exists := evidenceMap[e.Key] + if !exists { + evidenceMap[e.Key] = e + } + } + + evidence := make([]stringEvidence, 0) + + for _, e := range evidenceMap { + evidence = append(evidence, e) + } + + return evidence +} + +func (x *defaultEvidenceExtractor) extract(ctx hookstage.ModuleContext) ([]onpremise.Evidence, string, error) { + if ctx == nil { + return nil, "", errors.New("context is nil") + } + + suaStrings, err := x.getEvidenceStrings(ctx[evidenceFromSuaCtxKey]) + if err != nil { + return nil, "", errors.Wrap(err, "error extracting sua evidence") + } + headerString, err := x.getEvidenceStrings(ctx[evidenceFromHeadersCtxKey]) + if err != nil { + return nil, "", errors.Wrap(err, "error extracting header evidence") + } + + // Merge evidence from headers and SUA, sua has higher priority + evidenceStrings := merge(suaStrings, headerString) + + if len(evidenceStrings) > 0 { + userAgentE, exists := getEvidenceByKey(evidenceStrings, userAgentHeader) + if !exists { + return nil, "", errors.New("User-Agent not found") + } + + evidence := x.extractEvidenceFromStrings(evidenceStrings) + + return evidence, userAgentE.Value, nil + } + + return nil, "", nil +} + +func (x *defaultEvidenceExtractor) getEvidenceStrings(source interface{}) ([]stringEvidence, error) { + if source == nil { + return []stringEvidence{}, nil + } + + evidenceStrings, ok := source.([]stringEvidence) + if !ok { + return nil, errors.New("bad cast to []stringEvidence") + } + + return evidenceStrings, nil +} + +func (x *defaultEvidenceExtractor) extractEvidenceFromStrings(strEvidence []stringEvidence) []onpremise.Evidence { + evidenceResult := make([]onpremise.Evidence, len(strEvidence)) + for i, e := range strEvidence { + prefix := dd.HttpHeaderString + if e.Prefix == queryPrefix { + prefix = dd.HttpEvidenceQuery + } + + evidenceResult[i] = onpremise.Evidence{ + Prefix: prefix, + Key: e.Key, + Value: e.Value, + } + } + + return evidenceResult +} diff --git a/modules/fiftyonedegrees/devicedetection/evidence_extractor_test.go b/modules/fiftyonedegrees/devicedetection/evidence_extractor_test.go new file mode 100644 index 00000000000..6b2f9b3ea85 --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/evidence_extractor_test.go @@ -0,0 +1,256 @@ +package devicedetection + +import ( + "net/http" + "testing" + + "github.com/51Degrees/device-detection-go/v4/dd" + "github.com/prebid/prebid-server/v3/hooks/hookstage" + "github.com/stretchr/testify/assert" +) + +func TestFromHeaders(t *testing.T) { + extractor := newEvidenceExtractor() + + req := http.Request{ + Header: make(map[string][]string), + } + req.Header.Add("header", "Value") + req.Header.Add("Sec-CH-UA-Full-Version-List", "Chrome;12") + evidenceKeys := []dd.EvidenceKey{ + { + Prefix: dd.EvidencePrefix(10), + Key: "header", + }, + { + Prefix: dd.EvidencePrefix(10), + Key: "Sec-CH-UA-Full-Version-List", + }, + } + + evidence := extractor.fromHeaders(&req, evidenceKeys) + + assert.NotNil(t, evidence) + assert.NotEmpty(t, evidence) + assert.Equal(t, evidence[0].Value, "Value") + assert.Equal(t, evidence[0].Key, "header") + assert.Equal(t, evidence[1].Value, "Chrome;12") + assert.Equal(t, evidence[1].Key, "Sec-CH-UA-Full-Version-List") +} + +func TestFromSuaPayload(t *testing.T) { + tests := []struct { + name string + payload []byte + evidenceSize int + evidenceKeyOrder int + expectedKey string + expectedValue string + }{ + { + name: "from_SUA_tag", + payload: []byte(`{ + "device": { + "sua": { + "browsers": [ + { + "brand": "Google Chrome", + "version": ["121", "0", "6167", "184"] + } + ], + "platform": { + "brand": "macOS", + "version": ["14", "0", "0"] + }, + "architecture": "arm" + } + } + }`), + evidenceSize: 4, + evidenceKeyOrder: 0, + expectedKey: "Sec-Ch-Ua-Arch", + expectedValue: "arm", + }, + { + name: "from_UA_headers", + payload: []byte(`{ + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148", + "sua": { + "architecture": "arm" + } + } + }`), + evidenceSize: 2, + evidenceKeyOrder: 1, + expectedKey: "Sec-Ch-Ua-Arch", + expectedValue: "arm", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + extractor := newEvidenceExtractor() + + evidence := extractor.fromSuaPayload(tt.payload) + + assert.NotNil(t, evidence) + assert.NotEmpty(t, evidence) + assert.Equal(t, len(evidence), tt.evidenceSize) + assert.Equal(t, evidence[tt.evidenceKeyOrder].Key, tt.expectedKey) + assert.Equal(t, evidence[tt.evidenceKeyOrder].Value, tt.expectedValue) + }) + } +} + +func TestExtract(t *testing.T) { + uaEvidence1 := stringEvidence{ + Prefix: "ua1", + Key: userAgentHeader, + Value: "uav1", + } + uaEvidence2 := stringEvidence{ + Prefix: "ua2", + Key: userAgentHeader, + Value: "uav2", + } + evidence1 := stringEvidence{ + Prefix: "e1", + Key: "k1", + Value: "v1", + } + emptyEvidence := stringEvidence{ + Prefix: "empty", + Key: "e1", + Value: "", + } + + tests := []struct { + name string + ctx hookstage.ModuleContext + wantEvidenceCount int + wantUserAgent string + wantError bool + }{ + { + name: "nil", + ctx: nil, + wantError: true, + }, + { + name: "empty", + ctx: hookstage.ModuleContext{ + evidenceFromSuaCtxKey: []stringEvidence{}, + evidenceFromHeadersCtxKey: []stringEvidence{}, + }, + wantEvidenceCount: 0, + wantUserAgent: "", + }, + { + name: "from_headers", + ctx: hookstage.ModuleContext{ + evidenceFromHeadersCtxKey: []stringEvidence{uaEvidence1}, + }, + wantEvidenceCount: 1, + wantUserAgent: "uav1", + }, + { + name: "from_headers_no_user_agent", + ctx: hookstage.ModuleContext{ + evidenceFromHeadersCtxKey: []stringEvidence{evidence1}, + }, + wantError: true, + }, + { + name: "from_sua", + ctx: hookstage.ModuleContext{ + evidenceFromSuaCtxKey: []stringEvidence{uaEvidence1}, + }, + wantEvidenceCount: 1, + wantUserAgent: "uav1", + }, + { + name: "from_sua_no_user_agent", + ctx: hookstage.ModuleContext{ + evidenceFromSuaCtxKey: []stringEvidence{evidence1}, + }, + wantError: true, + }, + { + name: "from_headers_error", + ctx: hookstage.ModuleContext{ + evidenceFromHeadersCtxKey: "bad value", + }, + wantError: true, + }, + { + name: "from_sua_error", + ctx: hookstage.ModuleContext{ + evidenceFromHeadersCtxKey: []stringEvidence{}, + evidenceFromSuaCtxKey: "bad value", + }, + wantError: true, + }, + { + name: "from_sua_and_headers", + ctx: hookstage.ModuleContext{ + evidenceFromHeadersCtxKey: []stringEvidence{uaEvidence1}, + evidenceFromSuaCtxKey: []stringEvidence{evidence1}, + }, + wantEvidenceCount: 2, + wantUserAgent: "uav1", + }, + { + name: "from_sua_and_headers_sua_can_overwrite_if_ua_present", + ctx: hookstage.ModuleContext{ + evidenceFromHeadersCtxKey: []stringEvidence{uaEvidence1}, + evidenceFromSuaCtxKey: []stringEvidence{uaEvidence2}, + }, + wantEvidenceCount: 1, + wantUserAgent: "uav2", + }, + { + name: "empty_string_values", + ctx: hookstage.ModuleContext{ + evidenceFromHeadersCtxKey: []stringEvidence{emptyEvidence}, + }, + wantError: true, + }, + { + name: "empty_sua_values", + ctx: hookstage.ModuleContext{ + evidenceFromSuaCtxKey: []stringEvidence{emptyEvidence}, + }, + wantError: true, + }, + { + name: "mixed_valid_and_invalid", + ctx: hookstage.ModuleContext{ + evidenceFromHeadersCtxKey: []stringEvidence{uaEvidence1}, + evidenceFromSuaCtxKey: "bad value", + }, + wantError: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + extractor := newEvidenceExtractor() + evidence, userAgent, err := extractor.extract(test.ctx) + + if test.wantError { + assert.Error(t, err) + assert.Nil(t, evidence) + assert.Equal(t, userAgent, "") + } else if test.wantEvidenceCount == 0 { + assert.NoError(t, err) + assert.Nil(t, evidence) + assert.Equal(t, userAgent, "") + } else { + assert.NoError(t, err) + assert.Equal(t, len(evidence), test.wantEvidenceCount) + assert.Equal(t, userAgent, test.wantUserAgent) + } + }) + } +} diff --git a/modules/fiftyonedegrees/devicedetection/fiftyone_device_types.go b/modules/fiftyonedegrees/devicedetection/fiftyone_device_types.go new file mode 100644 index 00000000000..7237698117d --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/fiftyone_device_types.go @@ -0,0 +1,77 @@ +package devicedetection + +import ( + "github.com/prebid/openrtb/v20/adcom1" +) + +type deviceTypeMap = map[deviceType]adcom1.DeviceType + +var mobileOrTabletDeviceTypes = []deviceType{ + deviceTypeMobile, + deviceTypeSmartPhone, +} + +var personalComputerDeviceTypes = []deviceType{ + deviceTypeDesktop, + deviceTypeEReader, + deviceTypeVehicleDisplay, +} + +var tvDeviceTypes = []deviceType{ + deviceTypeTv, +} + +var phoneDeviceTypes = []deviceType{ + deviceTypePhone, +} + +var tabletDeviceTypes = []deviceType{ + deviceTypeTablet, +} + +var connectedDeviceTypes = []deviceType{ + deviceTypeIoT, + deviceTypeRouter, + deviceTypeSmallScreen, + deviceTypeSmartSpeaker, + deviceTypeSmartWatch, +} + +var setTopBoxDeviceTypes = []deviceType{ + deviceTypeMediaHub, + deviceTypeConsole, +} + +var oohDeviceTypes = []deviceType{ + deviceTypeKiosk, +} + +func applyCollection(items []deviceType, value adcom1.DeviceType, mappedCollection deviceTypeMap) { + for _, item := range items { + mappedCollection[item] = value + } +} + +var deviceTypeMapCollection = deviceTypeMap{} + +func init() { + applyCollection(mobileOrTabletDeviceTypes, adcom1.DeviceMobile, deviceTypeMapCollection) + applyCollection(personalComputerDeviceTypes, adcom1.DevicePC, deviceTypeMapCollection) + applyCollection(tvDeviceTypes, adcom1.DeviceTV, deviceTypeMapCollection) + applyCollection(phoneDeviceTypes, adcom1.DevicePhone, deviceTypeMapCollection) + applyCollection(tabletDeviceTypes, adcom1.DeviceTablet, deviceTypeMapCollection) + applyCollection(connectedDeviceTypes, adcom1.DeviceConnected, deviceTypeMapCollection) + applyCollection(setTopBoxDeviceTypes, adcom1.DeviceSetTopBox, deviceTypeMapCollection) + applyCollection(oohDeviceTypes, adcom1.DeviceOOH, deviceTypeMapCollection) +} + +// fiftyOneDtToRTB converts a 51Degrees device type to an OpenRTB device type. +// If the device type is not recognized, it defaults to PC. +func fiftyOneDtToRTB(val string) adcom1.DeviceType { + id, ok := deviceTypeMapCollection[deviceType(val)] + if ok { + return id + } + + return adcom1.DevicePC +} diff --git a/modules/fiftyonedegrees/devicedetection/fiftyone_device_types_test.go b/modules/fiftyonedegrees/devicedetection/fiftyone_device_types_test.go new file mode 100644 index 00000000000..5fd0203bac8 --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/fiftyone_device_types_test.go @@ -0,0 +1,90 @@ +package devicedetection + +import ( + "testing" + + "github.com/prebid/openrtb/v20/adcom1" + "github.com/stretchr/testify/assert" +) + +func TestFiftyOneDtToRTB(t *testing.T) { + cases := []struct { + fiftyOneDt string + rtbDt adcom1.DeviceType + }{ + { + fiftyOneDt: "Phone", + rtbDt: adcom1.DevicePhone, + }, + { + fiftyOneDt: "Console", + rtbDt: adcom1.DeviceSetTopBox, + }, + { + fiftyOneDt: "Desktop", + rtbDt: adcom1.DevicePC, + }, + { + fiftyOneDt: "EReader", + rtbDt: adcom1.DevicePC, + }, + { + fiftyOneDt: "IoT", + rtbDt: adcom1.DeviceConnected, + }, + { + fiftyOneDt: "Kiosk", + rtbDt: adcom1.DeviceOOH, + }, + { + fiftyOneDt: "MediaHub", + rtbDt: adcom1.DeviceSetTopBox, + }, + { + fiftyOneDt: "Mobile", + rtbDt: adcom1.DeviceMobile, + }, + { + fiftyOneDt: "Router", + rtbDt: adcom1.DeviceConnected, + }, + { + fiftyOneDt: "SmallScreen", + rtbDt: adcom1.DeviceConnected, + }, + { + fiftyOneDt: "SmartPhone", + rtbDt: adcom1.DeviceMobile, + }, + { + fiftyOneDt: "SmartSpeaker", + rtbDt: adcom1.DeviceConnected, + }, + { + fiftyOneDt: "SmartWatch", + rtbDt: adcom1.DeviceConnected, + }, + { + fiftyOneDt: "Tablet", + rtbDt: adcom1.DeviceTablet, + }, + { + fiftyOneDt: "Tv", + rtbDt: adcom1.DeviceTV, + }, + { + fiftyOneDt: "Vehicle Display", + rtbDt: adcom1.DevicePC, + }, + { + fiftyOneDt: "Unknown", + rtbDt: adcom1.DevicePC, + }, + } + + for _, c := range cases { + t.Run(c.fiftyOneDt, func(t *testing.T) { + assert.Equal(t, c.rtbDt, fiftyOneDtToRTB(c.fiftyOneDt)) + }) + } +} diff --git a/modules/fiftyonedegrees/devicedetection/hook_auction_entrypoint.go b/modules/fiftyonedegrees/devicedetection/hook_auction_entrypoint.go new file mode 100644 index 00000000000..7597daa8e00 --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/hook_auction_entrypoint.go @@ -0,0 +1,27 @@ +package devicedetection + +import ( + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/hooks/hookstage" +) + +// handleAuctionEntryPointRequestHook is a hookstage.HookFunc that is used to handle the auction entrypoint request hook. +func handleAuctionEntryPointRequestHook(cfg config, payload hookstage.EntrypointPayload, deviceDetector deviceDetector, evidenceExtractor evidenceExtractor, accountValidator accountValidator) (result hookstage.HookResult[hookstage.EntrypointPayload], err error) { + // if account/domain is not allowed, return failure + if !accountValidator.isAllowed(cfg, payload.Body) { + return hookstage.HookResult[hookstage.EntrypointPayload]{}, hookexecution.NewFailure("account not allowed") + } + // fetch evidence from headers and sua + evidenceFromHeaders := evidenceExtractor.fromHeaders(payload.Request, deviceDetector.getSupportedHeaders()) + evidenceFromSua := evidenceExtractor.fromSuaPayload(payload.Body) + + // create a Module context and set the evidence from headers, evidence from sua and dd enabled flag + moduleContext := make(hookstage.ModuleContext) + moduleContext[evidenceFromHeadersCtxKey] = evidenceFromHeaders + moduleContext[evidenceFromSuaCtxKey] = evidenceFromSua + moduleContext[ddEnabledCtxKey] = true + + return hookstage.HookResult[hookstage.EntrypointPayload]{ + ModuleContext: moduleContext, + }, nil +} diff --git a/modules/fiftyonedegrees/devicedetection/hook_raw_auction_request.go b/modules/fiftyonedegrees/devicedetection/hook_raw_auction_request.go new file mode 100644 index 00000000000..88d0686905d --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/hook_raw_auction_request.go @@ -0,0 +1,173 @@ +package devicedetection + +import ( + "fmt" + "math" + + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/hooks/hookstage" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +func handleAuctionRequestHook(ctx hookstage.ModuleInvocationContext, deviceDetector deviceDetector, evidenceExtractor evidenceExtractor) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { + var result hookstage.HookResult[hookstage.RawAuctionRequestPayload] + + // If the entrypoint hook was not configured, return the result without any changes + if ctx.ModuleContext == nil { + return result, hookexecution.NewFailure("entrypoint hook was not configured") + } + + result.ChangeSet.AddMutation( + func(rawPayload hookstage.RawAuctionRequestPayload) (hookstage.RawAuctionRequestPayload, error) { + evidence, ua, err := evidenceExtractor.extract(ctx.ModuleContext) + if err != nil { + return rawPayload, hookexecution.NewFailure("error extracting evidence %s", err) + } + if evidence == nil { + return rawPayload, hookexecution.NewFailure("error extracting evidence") + } + + deviceInfo, err := deviceDetector.getDeviceInfo(evidence, ua) + if err != nil { + return rawPayload, hookexecution.NewFailure("error getting device info %s", err) + } + + result, err := hydrateFields(deviceInfo, rawPayload) + if err != nil { + return rawPayload, hookexecution.NewFailure(fmt.Sprintf("error hydrating fields %s", err)) + } + + return result, nil + }, hookstage.MutationUpdate, + ) + + return result, nil +} + +// hydrateFields hydrates the fields in the raw auction request payload with the device information +func hydrateFields(fiftyOneDd *deviceInfo, payload hookstage.RawAuctionRequestPayload) (hookstage.RawAuctionRequestPayload, error) { + devicePayload := gjson.GetBytes(payload, "device") + dPV := devicePayload.Value() + if dPV == nil { + return payload, nil + } + + deviceObject := dPV.(map[string]any) + deviceObject = setMissingFields(deviceObject, fiftyOneDd) + deviceObject = signDeviceData(deviceObject, fiftyOneDd) + + return mergeDeviceIntoPayload(payload, deviceObject) +} + +// setMissingFields sets fields such as ["devicetype", "ua", "make", "os", "osv", "h", "w", "pxratio", "js", "geoFetch", "model", "ppi"] +// if they are not already present in the device object +func setMissingFields(deviceObj map[string]any, fiftyOneDd *deviceInfo) map[string]any { + optionalFields := map[string]func() any{ + "devicetype": func() any { + return fiftyOneDtToRTB(fiftyOneDd.DeviceType) + }, + "ua": func() any { + if fiftyOneDd.UserAgent != ddUnknown { + return fiftyOneDd.UserAgent + } + return nil + }, + "make": func() any { + if fiftyOneDd.HardwareVendor != ddUnknown { + return fiftyOneDd.HardwareVendor + } + return nil + }, + "os": func() any { + if fiftyOneDd.PlatformName != ddUnknown { + return fiftyOneDd.PlatformName + } + return nil + }, + "osv": func() any { + if fiftyOneDd.PlatformVersion != ddUnknown { + return fiftyOneDd.PlatformVersion + } + return nil + }, + "h": func() any { + return fiftyOneDd.ScreenPixelsHeight + }, + "w": func() any { + return fiftyOneDd.ScreenPixelsWidth + }, + "pxratio": func() any { + return fiftyOneDd.PixelRatio + }, + "js": func() any { + val := 0 + if fiftyOneDd.Javascript { + val = 1 + } + return val + }, + "geoFetch": func() any { + val := 0 + if fiftyOneDd.GeoLocation { + val = 1 + } + return val + }, + "model": func() any { + newVal := fiftyOneDd.HardwareModel + if newVal == ddUnknown { + newVal = fiftyOneDd.HardwareName + } + if newVal != ddUnknown { + return newVal + } + return nil + }, + "ppi": func() any { + if fiftyOneDd.ScreenPixelsHeight > 0 && fiftyOneDd.ScreenInchesHeight > 0 { + ppi := float64(fiftyOneDd.ScreenPixelsHeight) / fiftyOneDd.ScreenInchesHeight + return int(math.Round(ppi)) + } + return nil + }, + } + + for field, valFunc := range optionalFields { + _, ok := deviceObj[field] + if !ok { + val := valFunc() + if val != nil { + deviceObj[field] = val + } + } + } + + return deviceObj +} + +// signDeviceData signs the device data with the device information in the ext map of the device object +func signDeviceData(deviceObj map[string]any, fiftyOneDd *deviceInfo) map[string]any { + extObj, ok := deviceObj["ext"] + var ext map[string]any + if ok { + ext = extObj.(map[string]any) + } else { + ext = make(map[string]any) + } + + ext["fiftyonedegrees_deviceId"] = fiftyOneDd.DeviceId + deviceObj["ext"] = ext + + return deviceObj +} + +// mergeDeviceIntoPayload merges the modified device object back into the RawAuctionRequestPayload +func mergeDeviceIntoPayload(payload hookstage.RawAuctionRequestPayload, deviceObject map[string]any) (hookstage.RawAuctionRequestPayload, error) { + newPayload, err := sjson.SetBytes(payload, "device", deviceObject) + if err != nil { + return payload, err + } + + return newPayload, nil +} diff --git a/modules/fiftyonedegrees/devicedetection/models.go b/modules/fiftyonedegrees/devicedetection/models.go new file mode 100644 index 00000000000..c58daa211fd --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/models.go @@ -0,0 +1,66 @@ +package devicedetection + +// Prefixes in literal format +const queryPrefix = "query." +const headerPrefix = "header." +const ddUnknown = "Unknown" + +// Evidence where all fields are in string format +type stringEvidence struct { + Prefix string + Key string + Value string +} + +func getEvidenceByKey(e []stringEvidence, key string) (stringEvidence, bool) { + for _, evidence := range e { + if evidence.Key == key { + return evidence, true + } + } + return stringEvidence{}, false +} + +type deviceType string + +const ( + deviceTypePhone = "Phone" + deviceTypeConsole = "Console" + deviceTypeDesktop = "Desktop" + deviceTypeEReader = "EReader" + deviceTypeIoT = "IoT" + deviceTypeKiosk = "Kiosk" + deviceTypeMediaHub = "MediaHub" + deviceTypeMobile = "Mobile" + deviceTypeRouter = "Router" + deviceTypeSmallScreen = "SmallScreen" + deviceTypeSmartPhone = "SmartPhone" + deviceTypeSmartSpeaker = "SmartSpeaker" + deviceTypeSmartWatch = "SmartWatch" + deviceTypeTablet = "Tablet" + deviceTypeTv = "Tv" + deviceTypeVehicleDisplay = "Vehicle Display" +) + +type deviceInfo struct { + HardwareVendor string + HardwareName string + DeviceType string + PlatformVendor string + PlatformName string + PlatformVersion string + BrowserVendor string + BrowserName string + BrowserVersion string + ScreenPixelsWidth int64 + ScreenPixelsHeight int64 + PixelRatio float64 + Javascript bool + GeoLocation bool + HardwareFamily string + HardwareModel string + HardwareModelVariants string + UserAgent string + DeviceId string + ScreenInchesHeight float64 +} diff --git a/modules/fiftyonedegrees/devicedetection/models_test.go b/modules/fiftyonedegrees/devicedetection/models_test.go new file mode 100644 index 00000000000..898f25f4144 --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/models_test.go @@ -0,0 +1,63 @@ +package devicedetection + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetEvidenceByKey(t *testing.T) { + populatedEvidence := []stringEvidence{ + {Key: "key1", Value: "value1"}, + {Key: "key2", Value: "value2"}, + {Key: "key3", Value: "value3"}, + } + + tests := []struct { + name string + evidence []stringEvidence + key string + expectEvidence stringEvidence + expectFound bool + }{ + { + name: "nil_evidence", + evidence: nil, + key: "key2", + expectEvidence: stringEvidence{}, + expectFound: false, + }, + { + name: "empty_evidence", + evidence: []stringEvidence{}, + key: "key2", + expectEvidence: stringEvidence{}, + expectFound: false, + }, + { + name: "key_found", + evidence: populatedEvidence, + key: "key2", + expectEvidence: stringEvidence{ + Key: "key2", + Value: "value2", + }, + expectFound: true, + }, + { + name: "key_not_found", + evidence: populatedEvidence, + key: "key4", + expectEvidence: stringEvidence{}, + expectFound: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result, exists := getEvidenceByKey(test.evidence, test.key) + assert.Equal(t, test.expectFound, exists) + assert.Equal(t, test.expectEvidence, result) + }) + } +} diff --git a/modules/fiftyonedegrees/devicedetection/module.go b/modules/fiftyonedegrees/devicedetection/module.go new file mode 100644 index 00000000000..80eed36efda --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/module.go @@ -0,0 +1,107 @@ +package devicedetection + +import ( + "context" + "encoding/json" + "net/http" + + "github.com/51Degrees/device-detection-go/v4/dd" + "github.com/51Degrees/device-detection-go/v4/onpremise" + "github.com/pkg/errors" + "github.com/prebid/prebid-server/v3/hooks/hookstage" + "github.com/prebid/prebid-server/v3/modules/moduledeps" +) + +func configHashFromConfig(cfg *config) *dd.ConfigHash { + configHash := dd.NewConfigHash(cfg.getPerformanceProfile()) + if cfg.Performance.Concurrency != nil { + configHash.SetConcurrency(uint16(*cfg.Performance.Concurrency)) + } + + if cfg.Performance.Difference != nil { + configHash.SetDifference(int32(*cfg.Performance.Difference)) + } + + if cfg.Performance.AllowUnmatched != nil { + configHash.SetAllowUnmatched(*cfg.Performance.AllowUnmatched) + } + + if cfg.Performance.Drift != nil { + configHash.SetDrift(int32(*cfg.Performance.Drift)) + } + return configHash +} + +func Builder(rawConfig json.RawMessage, _ moduledeps.ModuleDeps) (interface{}, error) { + cfg, err := parseConfig(rawConfig) + if err != nil { + return Module{}, errors.Wrap(err, "failed to parse config") + } + + err = validateConfig(cfg) + if err != nil { + return nil, errors.Wrap(err, "invalid config") + } + + configHash := configHashFromConfig(&cfg) + + deviceDetectorImpl, err := newDeviceDetector( + configHash, + &cfg, + ) + if err != nil { + return nil, errors.Wrap(err, "failed to create device detector") + } + + return Module{ + cfg, + deviceDetectorImpl, + newEvidenceExtractor(), + newAccountValidator(), + }, + nil +} + +type Module struct { + config config + deviceDetector deviceDetector + evidenceExtractor evidenceExtractor + accountValidator accountValidator +} + +type deviceDetector interface { + getSupportedHeaders() []dd.EvidenceKey + getDeviceInfo(evidence []onpremise.Evidence, ua string) (*deviceInfo, error) +} + +type accountValidator interface { + isAllowed(cfg config, req []byte) bool +} + +type evidenceExtractor interface { + fromHeaders(request *http.Request, httpHeaderKeys []dd.EvidenceKey) []stringEvidence + fromSuaPayload(payload []byte) []stringEvidence + extract(ctx hookstage.ModuleContext) ([]onpremise.Evidence, string, error) +} + +func (m Module) HandleEntrypointHook( + _ context.Context, + _ hookstage.ModuleInvocationContext, + payload hookstage.EntrypointPayload, +) (hookstage.HookResult[hookstage.EntrypointPayload], error) { + return handleAuctionEntryPointRequestHook( + m.config, + payload, + m.deviceDetector, + m.evidenceExtractor, + m.accountValidator, + ) +} + +func (m Module) HandleRawAuctionHook( + _ context.Context, + mCtx hookstage.ModuleInvocationContext, + _ hookstage.RawAuctionRequestPayload, +) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) { + return handleAuctionRequestHook(mCtx, m.deviceDetector, m.evidenceExtractor) +} diff --git a/modules/fiftyonedegrees/devicedetection/module_test.go b/modules/fiftyonedegrees/devicedetection/module_test.go new file mode 100644 index 00000000000..eb59d01359e --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/module_test.go @@ -0,0 +1,703 @@ +package devicedetection + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "os" + "testing" + + "github.com/51Degrees/device-detection-go/v4/dd" + "github.com/51Degrees/device-detection-go/v4/onpremise" + "github.com/prebid/prebid-server/v3/hooks/hookstage" + "github.com/prebid/prebid-server/v3/modules/moduledeps" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +type mockAccValidator struct { + mock.Mock +} + +func (m *mockAccValidator) isAllowed(cfg config, req []byte) bool { + args := m.Called(cfg, req) + return args.Bool(0) +} + +type mockEvidenceExtractor struct { + mock.Mock +} + +func (m *mockEvidenceExtractor) fromHeaders(request *http.Request, httpHeaderKeys []dd.EvidenceKey) []stringEvidence { + args := m.Called(request, httpHeaderKeys) + + return args.Get(0).([]stringEvidence) +} + +func (m *mockEvidenceExtractor) fromSuaPayload(payload []byte) []stringEvidence { + args := m.Called(payload) + + return args.Get(0).([]stringEvidence) +} + +func (m *mockEvidenceExtractor) extract(ctx hookstage.ModuleContext) ([]onpremise.Evidence, string, error) { + args := m.Called(ctx) + + res := args.Get(0) + if res == nil { + return nil, args.String(1), args.Error(2) + } + + return res.([]onpremise.Evidence), args.String(1), args.Error(2) +} + +type mockDeviceDetector struct { + mock.Mock +} + +func (m *mockDeviceDetector) getSupportedHeaders() []dd.EvidenceKey { + args := m.Called() + return args.Get(0).([]dd.EvidenceKey) +} + +func (m *mockDeviceDetector) getDeviceInfo(evidence []onpremise.Evidence, ua string) (*deviceInfo, error) { + + args := m.Called(evidence, ua) + + res := args.Get(0) + + if res == nil { + return nil, args.Error(1) + } + + return res.(*deviceInfo), args.Error(1) +} + +func TestHandleEntrypointHookAccountNotAllowed(t *testing.T) { + var mockValidator mockAccValidator + + mockValidator.On("isAllowed", mock.Anything, mock.Anything).Return(false) + + module := Module{ + accountValidator: &mockValidator, + } + + _, err := module.HandleEntrypointHook(nil, hookstage.ModuleInvocationContext{}, hookstage.EntrypointPayload{}) + assert.Error(t, err) + assert.Equal(t, "hook execution failed: account not allowed", err.Error()) +} + +func TestHandleEntrypointHookAccountAllowed(t *testing.T) { + var mockValidator mockAccValidator + + mockValidator.On("isAllowed", mock.Anything, mock.Anything).Return(true) + + var mockEvidenceExtractor mockEvidenceExtractor + mockEvidenceExtractor.On("fromHeaders", mock.Anything, mock.Anything).Return( + []stringEvidence{{ + Prefix: "123", + Key: "key", + Value: "val", + }}, + ) + + mockEvidenceExtractor.On("fromSuaPayload", mock.Anything, mock.Anything).Return( + []stringEvidence{{ + Prefix: "123", + Key: "User-Agent", + Value: "ua", + }}, + ) + + var mockDeviceDetector mockDeviceDetector + + mockDeviceDetector.On("getSupportedHeaders").Return( + []dd.EvidenceKey{{ + Prefix: dd.HttpEvidenceQuery, + Key: "key", + }}, + ) + + module := Module{ + deviceDetector: &mockDeviceDetector, + evidenceExtractor: &mockEvidenceExtractor, + accountValidator: &mockValidator, + } + + result, err := module.HandleEntrypointHook(nil, hookstage.ModuleInvocationContext{}, hookstage.EntrypointPayload{}) + assert.NoError(t, err) + + assert.Equal( + t, result.ModuleContext[evidenceFromHeadersCtxKey], []stringEvidence{{ + Prefix: "123", + Key: "key", + Value: "val", + }}, + ) + + assert.Equal( + t, result.ModuleContext[evidenceFromSuaCtxKey], []stringEvidence{{ + Prefix: "123", + Key: "User-Agent", + Value: "ua", + }}, + ) +} + +func TestHandleRawAuctionHookNoCtx(t *testing.T) { + module := Module{} + + _, err := module.HandleRawAuctionHook( + nil, + hookstage.ModuleInvocationContext{}, + hookstage.RawAuctionRequestPayload{}, + ) + assert.Errorf(t, err, "entrypoint hook was not configured") +} + +func TestHandleRawAuctionHookExtractError(t *testing.T) { + var mockValidator mockAccValidator + + mockValidator.On("isAllowed", mock.Anything, mock.Anything).Return(true) + + var evidenceExtractorM mockEvidenceExtractor + evidenceExtractorM.On("extract", mock.Anything).Return( + nil, + "ua", + nil, + ) + + var mockDeviceDetector mockDeviceDetector + + module := Module{ + deviceDetector: &mockDeviceDetector, + evidenceExtractor: &evidenceExtractorM, + accountValidator: &mockValidator, + } + + mctx := make(hookstage.ModuleContext) + + mctx[ddEnabledCtxKey] = true + + result, err := module.HandleRawAuctionHook( + context.TODO(), hookstage.ModuleInvocationContext{ + ModuleContext: mctx, + }, + hookstage.RawAuctionRequestPayload{}, + ) + + assert.NoError(t, err) + assert.Equal(t, len(result.ChangeSet.Mutations()), 1) + assert.Equal(t, result.ChangeSet.Mutations()[0].Type(), hookstage.MutationUpdate) + + mutation := result.ChangeSet.Mutations()[0] + + body := []byte(`{}`) + + _, err = mutation.Apply(body) + assert.Errorf(t, err, "error extracting evidence") + + var mockEvidenceErrExtractor mockEvidenceExtractor + mockEvidenceErrExtractor.On("extract", mock.Anything).Return( + nil, + "", + errors.New("error"), + ) + + module.evidenceExtractor = &mockEvidenceErrExtractor + + result, err = module.HandleRawAuctionHook( + context.TODO(), hookstage.ModuleInvocationContext{ + ModuleContext: mctx, + }, + hookstage.RawAuctionRequestPayload{}, + ) + + assert.NoError(t, err) + + assert.Equal(t, len(result.ChangeSet.Mutations()), 1) + + assert.Equal(t, result.ChangeSet.Mutations()[0].Type(), hookstage.MutationUpdate) + + mutation = result.ChangeSet.Mutations()[0] + + _, err = mutation.Apply(body) + assert.Errorf(t, err, "error extracting evidence error") + +} + +func TestHandleRawAuctionHookEnrichment(t *testing.T) { + var mockValidator mockAccValidator + + mockValidator.On("isAllowed", mock.Anything, mock.Anything).Return(true) + + var mockEvidenceExtractor mockEvidenceExtractor + mockEvidenceExtractor.On("extract", mock.Anything).Return( + []onpremise.Evidence{ + { + Key: "key", + Value: "val", + }, + }, + "ua", + nil, + ) + + var deviceDetectorM mockDeviceDetector + + deviceDetectorM.On("getDeviceInfo", mock.Anything, mock.Anything).Return( + &deviceInfo{ + HardwareVendor: "Apple", + HardwareName: "Macbook", + DeviceType: "device", + PlatformVendor: "Apple", + PlatformName: "MacOs", + PlatformVersion: "14", + BrowserVendor: "Google", + BrowserName: "Crome", + BrowserVersion: "12", + ScreenPixelsWidth: 1024, + ScreenPixelsHeight: 1080, + PixelRatio: 223, + Javascript: true, + GeoLocation: true, + HardwareFamily: "Macbook", + HardwareModel: "Macbook", + HardwareModelVariants: "Macbook", + UserAgent: "ua", + DeviceId: "", + }, + nil, + ) + + module := Module{ + deviceDetector: &deviceDetectorM, + evidenceExtractor: &mockEvidenceExtractor, + accountValidator: &mockValidator, + } + + mctx := make(hookstage.ModuleContext) + mctx[ddEnabledCtxKey] = true + + result, err := module.HandleRawAuctionHook( + nil, hookstage.ModuleInvocationContext{ + ModuleContext: mctx, + }, + []byte{}, + ) + assert.NoError(t, err) + assert.Equal(t, len(result.ChangeSet.Mutations()), 1) + assert.Equal(t, result.ChangeSet.Mutations()[0].Type(), hookstage.MutationUpdate) + + mutation := result.ChangeSet.Mutations()[0] + + body := []byte(`{ + "device": { + "connectiontype": 2, + "ext": { + "atts": 0, + "ifv": "1B8EFA09-FF8F-4123-B07F-7283B50B3870" + }, + "sua": { + "source": 2, + "browsers": [ + { + "brand": "Not A(Brand", + "version": [ + "99", + "0", + "0", + "0" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "121", + "0", + "6167", + "184" + ] + }, + { + "brand": "Chromium", + "version": [ + "121", + "0", + "6167", + "184" + ] + } + ], + "platform": { + "brand": "macOS", + "version": [ + "14", + "0", + "0" + ] + }, + "mobile": 0, + "architecture": "arm", + "model": "" + } + } + }`) + + mutationResult, err := mutation.Apply(body) + + require.JSONEq(t, string(mutationResult), `{ + "device": { + "connectiontype": 2, + "ext": { + "atts": 0, + "ifv": "1B8EFA09-FF8F-4123-B07F-7283B50B3870", + "fiftyonedegrees_deviceId":"" + }, + "sua": { + "source": 2, + "browsers": [ + { + "brand": "Not A(Brand", + "version": [ + "99", + "0", + "0", + "0" + ] + }, + { + "brand": "Google Chrome", + "version": [ + "121", + "0", + "6167", + "184" + ] + }, + { + "brand": "Chromium", + "version": [ + "121", + "0", + "6167", + "184" + ] + } + ], + "platform": { + "brand": "macOS", + "version": [ + "14", + "0", + "0" + ] + }, + "mobile": 0, + "architecture": "arm", + "model": "" + } + ,"devicetype":2,"ua":"ua","make":"Apple","model":"Macbook","os":"MacOs","osv":"14","h":1080,"w":1024,"pxratio":223,"js":1,"geoFetch":1} + }`) + + var deviceDetectorErrM mockDeviceDetector + + deviceDetectorErrM.On("getDeviceInfo", mock.Anything, mock.Anything).Return( + nil, + errors.New("error"), + ) + + module.deviceDetector = &deviceDetectorErrM + + result, err = module.HandleRawAuctionHook( + nil, hookstage.ModuleInvocationContext{ + ModuleContext: mctx, + }, + []byte{}, + ) + + assert.NoError(t, err) + + assert.Equal(t, len(result.ChangeSet.Mutations()), 1) + + assert.Equal(t, result.ChangeSet.Mutations()[0].Type(), hookstage.MutationUpdate) + + mutation = result.ChangeSet.Mutations()[0] + + _, err = mutation.Apply(body) + assert.Errorf(t, err, "error getting device info") +} + +func TestHandleRawAuctionHookEnrichmentWithErrors(t *testing.T) { + var mockValidator mockAccValidator + + mockValidator.On("isAllowed", mock.Anything, mock.Anything).Return(true) + + var mockEvidenceExtractor mockEvidenceExtractor + mockEvidenceExtractor.On("extract", mock.Anything).Return( + []onpremise.Evidence{ + { + Key: "key", + Value: "val", + }, + }, + "ua", + nil, + ) + + var mockDeviceDetector mockDeviceDetector + + mockDeviceDetector.On("getDeviceInfo", mock.Anything, mock.Anything).Return( + &deviceInfo{ + HardwareVendor: "Apple", + HardwareName: "Macbook", + DeviceType: "device", + PlatformVendor: "Apple", + PlatformName: "MacOs", + PlatformVersion: "14", + BrowserVendor: "Google", + BrowserName: "Crome", + BrowserVersion: "12", + ScreenPixelsWidth: 1024, + ScreenPixelsHeight: 1080, + PixelRatio: 223, + Javascript: true, + GeoLocation: true, + HardwareFamily: "Macbook", + HardwareModel: "Macbook", + HardwareModelVariants: "Macbook", + UserAgent: "ua", + DeviceId: "", + ScreenInchesHeight: 7, + }, + nil, + ) + + module := Module{ + deviceDetector: &mockDeviceDetector, + evidenceExtractor: &mockEvidenceExtractor, + accountValidator: &mockValidator, + } + + mctx := make(hookstage.ModuleContext) + mctx[ddEnabledCtxKey] = true + + result, err := module.HandleRawAuctionHook( + nil, hookstage.ModuleInvocationContext{ + ModuleContext: mctx, + }, + []byte{}, + ) + assert.NoError(t, err) + assert.Equal(t, len(result.ChangeSet.Mutations()), 1) + assert.Equal(t, result.ChangeSet.Mutations()[0].Type(), hookstage.MutationUpdate) + + mutation := result.ChangeSet.Mutations()[0] + + mutationResult, err := mutation.Apply(hookstage.RawAuctionRequestPayload(`{"device":{}}`)) + assert.NoError(t, err) + require.JSONEq(t, string(mutationResult), `{"device":{"devicetype":2,"ua":"ua","make":"Apple","model":"Macbook","os":"MacOs","osv":"14","h":1080,"w":1024,"pxratio":223,"js":1,"geoFetch":1,"ppi":154,"ext":{"fiftyonedegrees_deviceId":""}}}`) +} + +func TestConfigHashFromConfig(t *testing.T) { + cfg := config{ + Performance: performance{ + Profile: "", + Concurrency: nil, + Difference: nil, + AllowUnmatched: nil, + Drift: nil, + }, + } + + result := configHashFromConfig(&cfg) + assert.Equal(t, result.PerformanceProfile(), dd.Default) + assert.Equal(t, result.Concurrency(), uint16(0xa)) + assert.Equal(t, result.Difference(), int32(0)) + assert.Equal(t, result.AllowUnmatched(), false) + assert.Equal(t, result.Drift(), int32(0)) + + concurrency := 1 + difference := 1 + allowUnmatched := true + drift := 1 + + cfg = config{ + Performance: performance{ + Profile: "Balanced", + Concurrency: &concurrency, + Difference: &difference, + AllowUnmatched: &allowUnmatched, + Drift: &drift, + }, + } + + result = configHashFromConfig(&cfg) + assert.Equal(t, result.PerformanceProfile(), dd.Balanced) + assert.Equal(t, result.Concurrency(), uint16(1)) + assert.Equal(t, result.Difference(), int32(1)) + assert.Equal(t, result.AllowUnmatched(), true) + assert.Equal(t, result.Drift(), int32(1)) + + cfg = config{ + Performance: performance{ + Profile: "InMemory", + }, + } + result = configHashFromConfig(&cfg) + assert.Equal(t, result.PerformanceProfile(), dd.InMemory) + + cfg = config{ + Performance: performance{ + Profile: "HighPerformance", + }, + } + result = configHashFromConfig(&cfg) + assert.Equal(t, result.PerformanceProfile(), dd.HighPerformance) +} + +func TestSignDeviceData(t *testing.T) { + devicePld := map[string]any{ + "ext": map[string]any{ + "my-key": "my-value", + }, + } + + deviceInfo := deviceInfo{ + DeviceId: "test-device-id", + } + + result := signDeviceData(devicePld, &deviceInfo) + r, err := json.Marshal(result) + if err != nil { + t.Fatalf("unexpected error: %s", err.Error()) + } + + require.JSONEq( + t, + `{"ext":{"fiftyonedegrees_deviceId":"test-device-id","my-key":"my-value"}}`, + string(r), + ) +} + +func TestBuilderWithInvalidJson(t *testing.T) { + _, err := Builder([]byte(`{`), moduledeps.ModuleDeps{}) + assert.Error(t, err) + assert.Errorf(t, err, "failed to parse config") +} + +func TestBuilderWithInvalidConfig(t *testing.T) { + _, err := Builder([]byte(`{"data_file":{}}`), moduledeps.ModuleDeps{}) + assert.Error(t, err) + assert.Errorf(t, err, "invalid config") +} + +func TestBuilderHandleDeviceDetectorError(t *testing.T) { + var mockConfig config + mockConfig.Performance.Profile = "default" + testFile, _ := os.Create("test-builder-config.hash") + defer testFile.Close() + defer os.Remove("test-builder-config.hash") + + _, err := Builder( + []byte(`{ + "enabled": true, + "data_file": { + "path": "test-builder-config.hash", + "update": { + "auto": true, + "url": "https://my.datafile.com/datafile.gz", + "polling_interval": 3600, + "licence_key": "your_licence_key", + "product": "V4Enterprise" + } + }, + "account_filter": {"allow_list": ["123"]}, + "performance": { + "profile": "123", + "concurrency": 1, + "difference": 1, + "allow_unmatched": true, + "drift": 1 + } + }`), moduledeps.ModuleDeps{}, + ) + assert.Error(t, err) + assert.Errorf(t, err, "failed to create device detector") +} + +func TestHydrateFields(t *testing.T) { + deviceInfo := &deviceInfo{ + HardwareVendor: "Apple", + HardwareName: "Macbook", + DeviceType: "device", + PlatformVendor: "Apple", + PlatformName: "MacOs", + PlatformVersion: "14", + BrowserVendor: "Google", + BrowserName: "Crome", + BrowserVersion: "12", + ScreenPixelsWidth: 1024, + ScreenPixelsHeight: 1080, + PixelRatio: 223, + Javascript: true, + GeoLocation: true, + HardwareFamily: "Macbook", + HardwareModel: "Macbook", + HardwareModelVariants: "Macbook", + UserAgent: "ua", + DeviceId: "dev-ide", + } + + rawPld := `{ + "imp": [{ + "id": "", + "banner": { + "topframe": 1, + "format": [ + { + "w": 728, + "h": 90 + } + ], + "pos": 1 + }, + "bidfloor": 0.01, + "bidfloorcur": "USD" + }], + "device": { + "model": "Macintosh", + "w": 843, + "h": 901, + "dnt": 0, + "ua": "Mozilla/5.0 (Linux; Android 13; SAMSUNG SM-A037U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/23.0 Chrome/115.0.0.0 Mobile Safari/537.36", + "language": "en", + "sua": {"browsers":[{"brand":"Not/A)Brand","version":["99","0","0","0"]},{"brand":"Samsung Internet","version":["23","0","1","1"]},{"brand":"Chromium","version":["115","0","5790","168"]}],"platform":{"brand":"Android","version":["13","0","0"]},"mobile":1,"model":"SM-A037U","source":2}, + "ext": {"h":"901","w":843} + }, + "cur": [ + "USD" + ], + "tmax": 1700 + }` + + payload, err := hydrateFields(deviceInfo, []byte(rawPld)) + assert.NoError(t, err) + + var deviceHolder struct { + Device json.RawMessage `json:"device"` + } + + err = json.Unmarshal(payload, &deviceHolder) + if err != nil { + t.Fatalf("unexpected error: %s", err.Error()) + } + + require.JSONEq( + t, + `{"devicetype":2,"dnt":0,"ext":{"fiftyonedegrees_deviceId":"dev-ide","h":"901","w":843},"geoFetch":1,"h":901,"js":1,"language":"en","make":"Apple","model":"Macintosh","os":"MacOs","osv":"14","pxratio":223,"sua":{"browsers":[{"brand":"Not/A)Brand","version":["99","0","0","0"]},{"brand":"Samsung Internet","version":["23","0","1","1"]},{"brand":"Chromium","version":["115","0","5790","168"]}],"mobile":1,"model":"SM-A037U","platform":{"brand":"Android","version":["13","0","0"]},"source":2},"ua":"Mozilla/5.0 (Linux; Android 13; SAMSUNG SM-A037U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/23.0 Chrome/115.0.0.0 Mobile Safari/537.36","w":843}`, + string(deviceHolder.Device), + ) +} diff --git a/modules/fiftyonedegrees/devicedetection/request_headers_extractor.go b/modules/fiftyonedegrees/devicedetection/request_headers_extractor.go new file mode 100644 index 00000000000..8440886b353 --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/request_headers_extractor.go @@ -0,0 +1,47 @@ +package devicedetection + +import ( + "net/http" + "strings" + + "github.com/51Degrees/device-detection-go/v4/dd" +) + +// evidenceFromRequestHeadersExtractor is a struct that extracts evidence from http request headers +type evidenceFromRequestHeadersExtractor struct{} + +func newEvidenceFromRequestHeadersExtractor() evidenceFromRequestHeadersExtractor { + return evidenceFromRequestHeadersExtractor{} +} + +func (x evidenceFromRequestHeadersExtractor) extract(request *http.Request, httpHeaderKeys []dd.EvidenceKey) []stringEvidence { + return x.extractEvidenceStrings(request, httpHeaderKeys) +} + +func (x evidenceFromRequestHeadersExtractor) extractEvidenceStrings(r *http.Request, keys []dd.EvidenceKey) []stringEvidence { + evidence := make([]stringEvidence, 0) + for _, e := range keys { + if e.Prefix == dd.HttpEvidenceQuery { + continue + } + + // Get evidence from headers + headerVal := r.Header.Get(e.Key) + if headerVal == "" { + continue + } + + if e.Key != secUaFullVersionList && e.Key != secChUa { + headerVal = strings.Replace(headerVal, "\"", "", -1) + } + + if headerVal != "" { + evidence = append(evidence, stringEvidence{ + Prefix: headerPrefix, + Key: e.Key, + Value: headerVal, + }) + } + } + return evidence +} diff --git a/modules/fiftyonedegrees/devicedetection/request_headers_extractor_test.go b/modules/fiftyonedegrees/devicedetection/request_headers_extractor_test.go new file mode 100644 index 00000000000..77fbed3a42f --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/request_headers_extractor_test.go @@ -0,0 +1,118 @@ +package devicedetection + +import ( + "net/http" + "testing" + + "github.com/51Degrees/device-detection-go/v4/dd" + "github.com/stretchr/testify/assert" +) + +func TestExtractEvidenceStrings(t *testing.T) { + tests := []struct { + name string + headers map[string]string + keys []dd.EvidenceKey + expectedEvidence []stringEvidence + }{ + { + name: "Ignored_query_evidence", + headers: map[string]string{ + "User-Agent": "Mozilla/5.0", + }, + keys: []dd.EvidenceKey{ + {Prefix: dd.HttpEvidenceQuery, Key: "User-Agent"}, + }, + expectedEvidence: []stringEvidence{}, + }, + { + name: "Empty_headers", + headers: map[string]string{}, + keys: []dd.EvidenceKey{ + {Prefix: dd.HttpHeaderString, Key: "User-Agent"}, + }, + expectedEvidence: []stringEvidence{}, + }, + { + name: "Single_header", + headers: map[string]string{ + "User-Agent": "Mozilla/5.0", + }, + keys: []dd.EvidenceKey{ + {Prefix: dd.HttpHeaderString, Key: "User-Agent"}, + }, + expectedEvidence: []stringEvidence{ + {Prefix: headerPrefix, Key: "User-Agent", Value: "Mozilla/5.0"}, + }, + }, + { + name: "Multiple_headers", + headers: map[string]string{ + "User-Agent": "Mozilla/5.0", + "Accept": "text/html", + }, + keys: []dd.EvidenceKey{ + {Prefix: dd.HttpHeaderString, Key: "User-Agent"}, + {Prefix: dd.HttpEvidenceQuery, Key: "Query"}, + {Prefix: dd.HttpHeaderString, Key: "Accept"}, + }, + expectedEvidence: []stringEvidence{ + {Prefix: headerPrefix, Key: "User-Agent", Value: "Mozilla/5.0"}, + {Prefix: headerPrefix, Key: "Accept", Value: "text/html"}, + }, + }, + { + name: "Header_with_quotes_removed", + headers: map[string]string{ + "IP-List": "\"92.0.4515.159\"", + }, + keys: []dd.EvidenceKey{ + {Prefix: dd.HttpHeaderString, Key: "IP-List"}, + }, + expectedEvidence: []stringEvidence{ + {Prefix: headerPrefix, Key: "IP-List", Value: "92.0.4515.159"}, + }, + }, + { + name: "Sec-CH-UA_headers_with_quotes_left", + headers: map[string]string{ + "Sec-CH-UA": "\"Chromium\";v=\"92\", \"Google Chrome\";v=\"92\"", + }, + keys: []dd.EvidenceKey{ + {Prefix: dd.HttpHeaderString, Key: secChUa}, + }, + expectedEvidence: []stringEvidence{ + {Prefix: headerPrefix, Key: secChUa, Value: "\"Chromium\";v=\"92\", \"Google Chrome\";v=\"92\""}, + }, + }, + { + name: "Sec-CH-UA-Full-Version-List_headers_with_quotes_left", + headers: map[string]string{ + "Sec-CH-UA-Full-Version-List": "\"92.0.4515.159\"", + }, + keys: []dd.EvidenceKey{ + {Prefix: dd.HttpHeaderString, Key: secUaFullVersionList}, + }, + expectedEvidence: []stringEvidence{ + {Prefix: headerPrefix, Key: secUaFullVersionList, Value: "\"92.0.4515.159\""}, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + req := http.Request{ + Header: make(map[string][]string), + } + + for key, value := range test.headers { + req.Header.Set(key, value) + } + + extractor := newEvidenceFromRequestHeadersExtractor() + evidence := extractor.extractEvidenceStrings(&req, test.keys) + + assert.Equal(t, test.expectedEvidence, evidence) + }) + } +} diff --git a/modules/fiftyonedegrees/devicedetection/sample/pbs.json b/modules/fiftyonedegrees/devicedetection/sample/pbs.json new file mode 100644 index 00000000000..43fd28610f1 --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/sample/pbs.json @@ -0,0 +1,84 @@ +{ + "adapters": [ + { + "appnexus": { + "enabled": true + } + } + ], + "gdpr": { + "enabled": true, + "default_value": 0, + "timeouts_ms": { + "active_vendorlist_fetch": 900000 + } + }, + "stored_requests": { + "filesystem": { + "enabled": true, + "directorypath": "sample/stored" + } + }, + "stored_responses": { + "filesystem": { + "enabled": true, + "directorypath": "sample/stored" + } + }, + "hooks": { + "enabled": true, + "modules": { + "fiftyonedegrees": { + "devicedetection": { + "enabled": true, + "data_file": { + "path": "TAC-HashV41.hash", + "update": { + "auto": false, + "polling_interval": 3600, + "license_key": "YOUR_LICENSE_KEY", + "product": "V4Enterprise" + } + }, + "performance": { + "profile": "InMemory" + } + } + } + }, + "host_execution_plan": { + "endpoints": { + "/openrtb2/auction": { + "stages": { + "entrypoint": { + "groups": [ + { + "timeout": 10, + "hook_sequence": [ + { + "module_code": "fiftyonedegrees.devicedetection", + "hook_impl_code": "fiftyone-devicedetection-entrypoint-hook" + } + ] + } + ] + }, + "raw_auction_request": { + "groups": [ + { + "timeout": 10, + "hook_sequence": [ + { + "module_code": "fiftyonedegrees.devicedetection", + "hook_impl_code": "fiftyone-devicedetection-raw-auction-request-hook" + } + ] + } + ] + } + } + } + } + } + } +} \ No newline at end of file diff --git a/modules/fiftyonedegrees/devicedetection/sample/request_data.json b/modules/fiftyonedegrees/devicedetection/sample/request_data.json new file mode 100644 index 00000000000..1f6bc8900f8 --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/sample/request_data.json @@ -0,0 +1,114 @@ +{ + "imp": [{ + "ext": { + "data": { + "adserver": { + "name": "gam", + "adslot": "test" + }, + "pbadslot": "test", + "gpid": "test" + }, + "gpid": "test", + "prebid": { + "bidder": { + "appnexus": { + "placement_id": 1, + "use_pmt_rule": false + } + }, + "adunitcode": "25e8ad9f-13a4-4404-ba74-f9eebff0e86c", + "floors": { + "floorMin": 0.01 + } + } + }, + "id": "2529eeea-813e-4da6-838f-f91c28d64867", + "banner": { + "topframe": 1, + "format": [ + { + "w": 728, + "h": 90 + } + ], + "pos": 1 + }, + "bidfloor": 0.01, + "bidfloorcur": "USD" + }], + "site": { + "domain": "test.com", + "publisher": { + "domain": "test.com", + "id": "1" + }, + "page": "https://www.test.com/" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 11; SM-G998W) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Mobile Safari/537.36" + }, + "id": "fc4670ce-4985-4316-a245-b43c885dc37a", + "test": 1, + "cur": [ + "USD" + ], + "source": { + "ext": { + "schain": { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "example.com", + "sid": "1234", + "hp": 1 + } + ] + } + } + }, + "ext": { + "prebid": { + "cache": { + "bids": { + "returnCreative": true + }, + "vastxml": { + "returnCreative": true + } + }, + "auctiontimestamp": 1698390609882, + "targeting": { + "includewinners": true, + "includebidderkeys": false + }, + "schains": [ + { + "bidders": [ + "appnexus" + ], + "schain": { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "example.com", + "sid": "1234", + "hp": 1 + } + ] + } + } + ], + "floors": { + "enabled": false, + "floorMin": 0.01, + "floorMinCur": "USD" + }, + "createtids": false + } + }, + "user": {}, + "tmax": 1700 +} \ No newline at end of file diff --git a/modules/fiftyonedegrees/devicedetection/sua_payload_extractor.go b/modules/fiftyonedegrees/devicedetection/sua_payload_extractor.go new file mode 100644 index 00000000000..ab69210449f --- /dev/null +++ b/modules/fiftyonedegrees/devicedetection/sua_payload_extractor.go @@ -0,0 +1,144 @@ +package devicedetection + +import ( + "fmt" + "strings" + + "github.com/spf13/cast" + "github.com/tidwall/gjson" +) + +const ( + secChUaArch = "Sec-Ch-Ua-Arch" + secChUaMobile = "Sec-Ch-Ua-Mobile" + secChUaModel = "Sec-Ch-Ua-Model" + secChUaPlatform = "Sec-Ch-Ua-Platform" + secUaFullVersionList = "Sec-Ch-Ua-Full-Version-List" + secChUaPlatformVersion = "Sec-Ch-Ua-Platform-Version" + secChUa = "Sec-Ch-Ua" + + userAgentHeader = "User-Agent" +) + +// evidenceFromSUAPayloadExtractor extracts evidence from the SUA payload of device +type evidenceFromSUAPayloadExtractor struct{} + +func newEvidenceFromSUAPayloadExtractor() evidenceFromSUAPayloadExtractor { + return evidenceFromSUAPayloadExtractor{} +} + +// Extract extracts evidence from the SUA payload +func (x evidenceFromSUAPayloadExtractor) extract(payload []byte) []stringEvidence { + if payload != nil { + return x.extractEvidenceStrings(payload) + } + + return nil +} + +var ( + uaPath = "device.ua" + archPath = "device.sua.architecture" + mobilePath = "device.sua.mobile" + modelPath = "device.sua.model" + platformBrandPath = "device.sua.platform.brand" + platformVersionPath = "device.sua.platform.version" + browsersPath = "device.sua.browsers" +) + +// extractEvidenceStrings extracts evidence from the SUA payload +func (x evidenceFromSUAPayloadExtractor) extractEvidenceStrings(payload []byte) []stringEvidence { + res := make([]stringEvidence, 0, 10) + + uaResult := gjson.GetBytes(payload, uaPath) + if uaResult.Exists() { + res = append( + res, + stringEvidence{Prefix: headerPrefix, Key: userAgentHeader, Value: uaResult.String()}, + ) + } + + archResult := gjson.GetBytes(payload, archPath) + if archResult.Exists() { + res = x.appendEvidenceIfExists(res, secChUaArch, archResult.String()) + } + + mobileResult := gjson.GetBytes(payload, mobilePath) + if mobileResult.Exists() { + res = x.appendEvidenceIfExists(res, secChUaMobile, mobileResult.String()) + } + + modelResult := gjson.GetBytes(payload, modelPath) + if modelResult.Exists() { + res = x.appendEvidenceIfExists(res, secChUaModel, modelResult.String()) + } + + platformBrandResult := gjson.GetBytes(payload, platformBrandPath) + if platformBrandResult.Exists() { + res = x.appendEvidenceIfExists(res, secChUaPlatform, platformBrandResult.String()) + } + + platformVersionResult := gjson.GetBytes(payload, platformVersionPath) + if platformVersionResult.Exists() { + res = x.appendEvidenceIfExists( + res, + secChUaPlatformVersion, + strings.Join(resultToStringArray(platformVersionResult.Array()), "."), + ) + } + + browsersResult := gjson.GetBytes(payload, browsersPath) + if browsersResult.Exists() { + res = x.appendEvidenceIfExists(res, secUaFullVersionList, x.extractBrowsers(browsersResult)) + + } + + return res +} + +func resultToStringArray(array []gjson.Result) []string { + strArray := make([]string, len(array)) + for i, result := range array { + strArray[i] = result.String() + } + + return strArray +} + +// appendEvidenceIfExists appends evidence to the destination if the value is not nil +func (x evidenceFromSUAPayloadExtractor) appendEvidenceIfExists(destination []stringEvidence, name string, value interface{}) []stringEvidence { + if value != nil { + valStr := cast.ToString(value) + if len(valStr) == 0 { + return destination + } + + return append( + destination, + stringEvidence{Prefix: headerPrefix, Key: name, Value: valStr}, + ) + } + + return destination +} + +// extractBrowsers extracts browsers from the SUA payload +func (x evidenceFromSUAPayloadExtractor) extractBrowsers(browsers gjson.Result) string { + if !browsers.IsArray() { + return "" + } + + browsersRaw := make([]string, len(browsers.Array())) + + for i, result := range browsers.Array() { + brand := result.Get("brand").String() + versionsRaw := result.Get("version").Array() + versions := resultToStringArray(versionsRaw) + + browsersRaw[i] = fmt.Sprintf(`"%s";v="%s"`, brand, strings.Join(versions, ".")) + } + + res := strings.Join(browsersRaw, ",") + + return res +} diff --git a/modules/generator/builder.tmpl b/modules/generator/builder.tmpl index f89cc21c87f..211e0d1e085 100644 --- a/modules/generator/builder.tmpl +++ b/modules/generator/builder.tmpl @@ -1,20 +1,21 @@ package modules - -{{if .}} import ( - {{- range .}} - {{.Vendor}}{{.Module | Title}} "github.com/prebid/prebid-server/modules/{{.Vendor}}/{{.Module}}" +{{- range $vendor, $modules := .}} + {{- range $module := $modules}} + {{$vendor}}{{$module | Title}} "github.com/prebid/prebid-server/v3/modules/{{$vendor}}/{{$module}}" {{- end}} +{{- end}} ) -{{end}} // builders returns mapping between module name and its builder // vendor and module names are chosen based on the module directory name func builders() ModuleBuilders { return ModuleBuilders{ - {{- range .}} - "{{.Vendor}}": { - "{{.Module}}": {{.Vendor}}{{.Module | Title}}.Builder, + {{- range $vendor, $modules := .}} + "{{$vendor}}": { + {{- range $module := $modules}} + "{{$module}}": {{$vendor}}{{$module | Title}}.Builder, + {{- end}} }, {{- end}} } diff --git a/modules/generator/buildergen.go b/modules/generator/buildergen.go index b906682b7a3..219932420bd 100644 --- a/modules/generator/buildergen.go +++ b/modules/generator/buildergen.go @@ -10,6 +10,7 @@ import ( "os" "path/filepath" "regexp" + "sort" "strings" "text/template" ) @@ -20,26 +21,26 @@ var ( outName = "builder.go" ) -type Module struct { - Vendor string - Module string -} - func main() { - var modules []Module + modules := make(map[string][]string) filepath.WalkDir("./", func(path string, d fs.DirEntry, err error) error { if !r.MatchString(path) { return nil } match := r.FindStringSubmatch(path) - modules = append(modules, Module{ - Vendor: match[1], - Module: match[2], - }) + vendorModules := modules[match[1]] + vendorModules = append(vendorModules, match[2]) + modules[match[1]] = vendorModules + return nil }) + for vendorName, names := range modules { + sort.Strings(names) + modules[vendorName] = names + } + funcMap := template.FuncMap{"Title": strings.Title} t, err := template.New(tmplName).Funcs(funcMap).ParseFiles(fmt.Sprintf("generator/%s", tmplName)) if err != nil { diff --git a/modules/helpers.go b/modules/helpers.go index c7fe9f73f31..c38b224931e 100644 --- a/modules/helpers.go +++ b/modules/helpers.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v3/hooks" + "github.com/prebid/prebid-server/v3/hooks/hookstage" ) var moduleReplacer = strings.NewReplacer(".", "_", "-", "_") diff --git a/modules/moduledeps/deps.go b/modules/moduledeps/deps.go index 08b18349223..266e22575e5 100644 --- a/modules/moduledeps/deps.go +++ b/modules/moduledeps/deps.go @@ -1,9 +1,14 @@ package moduledeps -import "net/http" +import ( + "net/http" + + "github.com/prebid/prebid-server/v3/currency" +) // ModuleDeps provides dependencies that custom modules may need for hooks execution. // Additional dependencies can be added here if modules need something more. type ModuleDeps struct { - HTTPClient *http.Client + HTTPClient *http.Client + RateConvertor *currency.RateConverter } diff --git a/modules/modules.go b/modules/modules.go index 4e60a9eda32..3b266a14c5b 100644 --- a/modules/modules.go +++ b/modules/modules.go @@ -5,10 +5,10 @@ import ( "fmt" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/modules/moduledeps" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/hooks" + "github.com/prebid/prebid-server/v3/modules/moduledeps" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) //go:generate go run ./generator/buildergen.go diff --git a/modules/modules_test.go b/modules/modules_test.go index f88a48a2c44..b6c59d73168 100644 --- a/modules/modules_test.go +++ b/modules/modules_test.go @@ -9,10 +9,10 @@ import ( "net/http" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/modules/moduledeps" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/hooks" + "github.com/prebid/prebid-server/v3/hooks/hookstage" + "github.com/prebid/prebid-server/v3/modules/moduledeps" "github.com/stretchr/testify/assert" ) diff --git a/modules/prebid/ortb2blocking/analytics.go b/modules/prebid/ortb2blocking/analytics.go index 4026b6722b9..bab50e3a4e9 100644 --- a/modules/prebid/ortb2blocking/analytics.go +++ b/modules/prebid/ortb2blocking/analytics.go @@ -1,8 +1,8 @@ package ortb2blocking import ( - "github.com/prebid/prebid-server/hooks/hookanalytics" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/prebid-server/v3/hooks/hookanalytics" + "github.com/prebid/prebid-server/v3/hooks/hookstage" ) const enforceBlockingTag = "enforce_blocking" diff --git a/modules/prebid/ortb2blocking/config.go b/modules/prebid/ortb2blocking/config.go index 74841eb5b33..1990b0a56aa 100644 --- a/modules/prebid/ortb2blocking/config.go +++ b/modules/prebid/ortb2blocking/config.go @@ -4,8 +4,8 @@ import ( "encoding/json" "fmt" - "github.com/prebid/openrtb/v19/adcom1" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/openrtb/v20/adcom1" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) func newConfig(data json.RawMessage) (config, error) { diff --git a/modules/prebid/ortb2blocking/config_test.go b/modules/prebid/ortb2blocking/config_test.go index 55ca3d21807..067e0786930 100644 --- a/modules/prebid/ortb2blocking/config_test.go +++ b/modules/prebid/ortb2blocking/config_test.go @@ -3,7 +3,7 @@ package ortb2blocking import ( "testing" - "github.com/prebid/openrtb/v19/adcom1" + "github.com/prebid/openrtb/v20/adcom1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/modules/prebid/ortb2blocking/hook_bidderrequest.go b/modules/prebid/ortb2blocking/hook_bidderrequest.go index 8f7ce42021c..4b3cb87c855 100644 --- a/modules/prebid/ortb2blocking/hook_bidderrequest.go +++ b/modules/prebid/ortb2blocking/hook_bidderrequest.go @@ -5,22 +5,22 @@ import ( "fmt" "strings" - "github.com/prebid/openrtb/v19/adcom1" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/adcom1" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/hooks/hookstage" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func handleBidderRequestHook( cfg config, payload hookstage.BidderRequestPayload, ) (result hookstage.HookResult[hookstage.BidderRequestPayload], err error) { - if payload.BidRequest == nil { - return result, hookexecution.NewFailure("empty BidRequest provided") + if payload.Request == nil || payload.Request.BidRequest == nil { + return result, hookexecution.NewFailure("payload contains a nil bid request") } - mediaTypes := mediaTypesFrom(payload.BidRequest) + mediaTypes := mediaTypesFrom(payload.Request.BidRequest) changeSet := hookstage.ChangeSet[hookstage.BidderRequestPayload]{} blockingAttributes := blockingAttributes{} @@ -60,7 +60,7 @@ func updateBAdv( result *hookstage.HookResult[hookstage.BidderRequestPayload], changeSet *hookstage.ChangeSet[hookstage.BidderRequestPayload], ) (err error) { - if len(payload.BidRequest.BAdv) > 0 { + if len(payload.Request.BAdv) > 0 { return nil } @@ -87,7 +87,7 @@ func updateBApp( result *hookstage.HookResult[hookstage.BidderRequestPayload], changeSet *hookstage.ChangeSet[hookstage.BidderRequestPayload], ) (err error) { - if len(payload.BidRequest.BApp) > 0 { + if len(payload.Request.BApp) > 0 { return nil } @@ -114,7 +114,7 @@ func updateBCat( result *hookstage.HookResult[hookstage.BidderRequestPayload], changeSet *hookstage.ChangeSet[hookstage.BidderRequestPayload], ) (err error) { - if len(payload.BidRequest.BCat) > 0 { + if len(payload.Request.BCat) > 0 { return nil } @@ -191,7 +191,7 @@ func updateCatTax( attributes *blockingAttributes, changeSet *hookstage.ChangeSet[hookstage.BidderRequestPayload], ) { - if payload.BidRequest.CatTax > 0 { + if payload.Request.CatTax > 0 { return } @@ -226,7 +226,7 @@ func mutationForImp( impUpdater impUpdateFunc, ) hookstage.MutationFunc[hookstage.BidderRequestPayload] { return func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) { - for i, imp := range payload.BidRequest.Imp { + for i, imp := range payload.Request.Imp { if values, ok := valuesByImp[imp.ID]; ok { if len(values) == 0 { continue @@ -236,7 +236,7 @@ func mutationForImp( imp.Banner = &openrtb2.Banner{} } - payload.BidRequest.Imp[i] = impUpdater(imp, values) + payload.Request.Imp[i] = impUpdater(imp, values) } } return payload, nil @@ -310,7 +310,7 @@ func findImpressionOverrides( overrides := map[string][]int{} messages := []string{} - for _, imp := range payload.BidRequest.Imp { + for _, imp := range payload.Request.Imp { // do not add override for attribute if it already exists in request if isAttrPresent(imp) { continue diff --git a/modules/prebid/ortb2blocking/hook_raw_bidder_response.go b/modules/prebid/ortb2blocking/hook_raw_bidder_response.go index 1c51256211b..a6ddbdd58c3 100644 --- a/modules/prebid/ortb2blocking/hook_raw_bidder_response.go +++ b/modules/prebid/ortb2blocking/hook_raw_bidder_response.go @@ -5,11 +5,11 @@ import ( "fmt" "strings" - "github.com/prebid/openrtb/v19/adcom1" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" + "github.com/prebid/openrtb/v20/adcom1" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/hooks/hookstage" ) func handleRawBidderResponseHook( @@ -34,7 +34,7 @@ func handleRawBidderResponseHook( // allowedBids will store all bids that have passed the attribute check allowedBids := make([]*adapters.TypedBid, 0) - for _, bid := range payload.Bids { + for _, bid := range payload.BidderResponse.Bids { failedChecksData := make(map[string]interface{}) bidMediaTypes := mediaTypesFromBid(bid) @@ -77,8 +77,8 @@ func handleRawBidderResponseHook( } changeSet := hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} - if len(payload.Bids) != len(allowedBids) { - changeSet.RawBidderResponse().Bids().Update(allowedBids) + if len(payload.BidderResponse.Bids) != len(allowedBids) { + changeSet.RawBidderResponse().Bids().UpdateBids(allowedBids) result.ChangeSet = changeSet } diff --git a/modules/prebid/ortb2blocking/module.go b/modules/prebid/ortb2blocking/module.go index 7386aa62bad..24ff28df746 100644 --- a/modules/prebid/ortb2blocking/module.go +++ b/modules/prebid/ortb2blocking/module.go @@ -4,9 +4,9 @@ import ( "context" "encoding/json" - "github.com/prebid/openrtb/v19/adcom1" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/modules/moduledeps" + "github.com/prebid/openrtb/v20/adcom1" + "github.com/prebid/prebid-server/v3/hooks/hookstage" + "github.com/prebid/prebid-server/v3/modules/moduledeps" ) func Builder(_ json.RawMessage, _ moduledeps.ModuleDeps) (interface{}, error) { diff --git a/modules/prebid/ortb2blocking/module_test.go b/modules/prebid/ortb2blocking/module_test.go index ecae5ca8c0a..17008b2dad6 100644 --- a/modules/prebid/ortb2blocking/module_test.go +++ b/modules/prebid/ortb2blocking/module_test.go @@ -6,13 +6,14 @@ import ( "errors" "testing" - "github.com/prebid/openrtb/v19/adcom1" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/hooks/hookanalytics" - "github.com/prebid/prebid-server/hooks/hookexecution" - "github.com/prebid/prebid-server/hooks/hookstage" - "github.com/prebid/prebid-server/modules/moduledeps" + "github.com/prebid/openrtb/v20/adcom1" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/hooks/hookanalytics" + "github.com/prebid/prebid-server/v3/hooks/hookexecution" + "github.com/prebid/prebid-server/v3/hooks/hookstage" + "github.com/prebid/prebid-server/v3/modules/moduledeps" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -497,7 +498,7 @@ func TestHandleBidderRequestHook(t *testing.T) { bidRequest: nil, expectedBidRequest: nil, expectedHookResult: hookstage.HookResult[hookstage.BidderRequestPayload]{}, - expectedError: hookexecution.NewFailure("empty BidRequest provided"), + expectedError: hookexecution.NewFailure("payload contains a nil bid request"), }, { description: "Expect baadv error if bidders and media_types not defined in config conditions", @@ -566,7 +567,8 @@ func TestHandleBidderRequestHook(t *testing.T) { for _, test := range testCases { t.Run(test.description, func(t *testing.T) { - payload := hookstage.BidderRequestPayload{Bidder: test.bidder, BidRequest: test.bidRequest} + brw := openrtb_ext.RequestWrapper{BidRequest: test.bidRequest} + payload := hookstage.BidderRequestPayload{Bidder: test.bidder, Request: &brw} result, err := Builder(nil, moduledeps.ModuleDeps{}) assert.NoError(t, err, "Failed to build module.") @@ -590,7 +592,8 @@ func TestHandleBidderRequestHook(t *testing.T) { _, err := mut.Apply(payload) assert.NoError(t, err) } - assert.Equal(t, test.expectedBidRequest, payload.BidRequest, "Invalid BidRequest after executing BidderRequestHook.") + + assert.Equal(t, test.expectedBidRequest, payload.Request.BidRequest, "Invalid BidRequest after executing BidderRequestHook.") // reset ChangeSet not to break hookResult assertion, we validated ChangeSet separately hookResult.ChangeSet = hookstage.ChangeSet[hookstage.BidderRequestPayload]{} @@ -611,11 +614,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }{ { description: "Payload not changed when empty account config and empty module contexts are provided. Analytic tags have successful records", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1}, + }, + }}, + }, expectedBids: []*adapters.TypedBid{ { Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1}, @@ -640,9 +645,11 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Catch error if wrong data has been passed from previous hook. Payload not changed", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1}, + }, }, }}, expectedBids: []*adapters.TypedBid{ @@ -655,14 +662,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by badv attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true}}}`), expectedBids: []*adapters.TypedBid{ { @@ -698,14 +707,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked because blocking conditions for current bidder do not exist. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true}}}`), expectedBids: []*adapters.TypedBid{ { @@ -739,14 +750,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked because enforce blocking is disabled by account config. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": false}}}`), expectedBids: []*adapters.TypedBid{ { @@ -780,14 +793,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked because enforce blocking overridden for given bidder. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {"bidders": ["appnexus"]}, "override": false}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -821,14 +836,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by badv attribute check (block unknown attributes). Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "block_unknown_adomain": true}}}`), expectedBids: []*adapters.TypedBid{ { @@ -864,14 +881,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked because block unknown overridden for given bidder. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "block_unknown_adomain": true, "action_overrides": {"block_unknown_adomain": [{"conditions": {"bidders": ["appnexus"]}, "override": false}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -905,14 +924,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid not blocked due to deal exception. Payload not updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1, DealID: "acceptDealID"}, - }, - { - Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1, DealID: "acceptDealID"}, + }, + { + Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "action_overrides": {"allowed_adomain_for_deals": [{"conditions": {"deal_ids": ["acceptDealID"]}, "override": ["forbidden_domain"]}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -946,11 +967,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing enforce blocks overrides for badv attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"badv": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -971,11 +994,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing block unknown domains overrides for badv attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"badv": {"enforce_blocks": true, "action_overrides": {"block_unknown_adomain": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -996,11 +1021,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if deal_ids not defined in config override conditions for badv attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, DealID: "acceptDealID"}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, DealID: "acceptDealID"}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"badv": {"enforce_blocks": true, "action_overrides": {"allowed_adomain_for_deals": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1021,14 +1048,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by bcat attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", Cat: []string{"moto"}, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", Cat: []string{"moto"}, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"bcat":{"enforce_blocks": true}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1064,11 +1093,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing enforce blocks overrides for bcat attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"bcat": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1089,11 +1120,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing block unknown domains overrides for bcat attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"bcat": {"enforce_blocks": true, "action_overrides": {"block_unknown_adv_cat": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1114,11 +1147,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if deal_ids not defined in config override conditions for bcat attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1, DealID: "acceptDealID"}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1, DealID: "acceptDealID"}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"bcat": {"enforce_blocks": true, "action_overrides": {"allowed_adv_cat_for_deals": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1139,14 +1174,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by cattax attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", CatTax: 1, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", CatTax: 1, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"bcat":{"enforce_blocks": true}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1182,14 +1219,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by cattax attribute check (the default value used if no blocking attribute passed). Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", CatTax: 1, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", CatTax: 1, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"bcat":{"enforce_blocks": true}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1224,14 +1263,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by bapp attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", Bundle: "allowed_bundle", ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", Bundle: "allowed_bundle", ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"bapp":{"enforce_blocks": true}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1267,11 +1308,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing enforce blocks overrides for bapp attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"bapp": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1292,11 +1335,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if deal_ids not defined in config override conditions for bapp attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1, DealID: "acceptDealID"}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1, DealID: "acceptDealID"}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"bapp": {"enforce_blocks": true, "action_overrides": {"allowed_app_for_deals": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1317,14 +1362,16 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by battr attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1}, - }, - { - Bid: &openrtb2.Bid{ID: "2", Attr: []adcom1.CreativeAttribute{2}, ImpID: impID2}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1}, + }, + { + Bid: &openrtb2.Bid{ID: "2", Attr: []adcom1.CreativeAttribute{2}, ImpID: impID2}, + }, + }}, + }, config: json.RawMessage(`{"attributes":{"battr":{"enforce_blocks": true}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1360,11 +1407,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if there is an issue processing enforce blocks overrides for battr attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"battr": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1385,11 +1434,13 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Expect error if deal_ids not defined in config override conditions for battr attribute. Analytics should have error status tag", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1, DealID: "acceptDealID"}, - }, - }}, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1, DealID: "acceptDealID"}, + }, + }}, + }, config: json.RawMessage(`{"attributes": {"battr": {"enforce_blocks": true, "action_overrides": {"allowed_banner_attr_for_deals": [{"conditions": {}}]}}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1410,30 +1461,32 @@ func TestHandleRawBidderResponseHook(t *testing.T) { }, { description: "Bid blocked by multiple attribute check. Payload updated. Analytic tags successfully collected", - payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{ - { - Bid: &openrtb2.Bid{ - ID: "1", - ADomain: []string{"forbidden_domain"}, - Cat: []string{"fishing"}, - CatTax: 1, - Bundle: "forbidden_bundle", - Attr: []adcom1.CreativeAttribute{1}, - ImpID: impID1, + payload: hookstage.RawBidderResponsePayload{Bidder: bidder, BidderResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{ + ID: "1", + ADomain: []string{"forbidden_domain"}, + Cat: []string{"fishing"}, + CatTax: 1, + Bundle: "forbidden_bundle", + Attr: []adcom1.CreativeAttribute{1}, + ImpID: impID1, + }, }, - }, - { - Bid: &openrtb2.Bid{ - ID: "2", - ADomain: []string{"allowed_domain"}, - Cat: []string{"moto"}, - CatTax: 2, - Bundle: "allowed_bundle", - Attr: []adcom1.CreativeAttribute{2}, - ImpID: impID2, + { + Bid: &openrtb2.Bid{ + ID: "2", + ADomain: []string{"allowed_domain"}, + Cat: []string{"moto"}, + CatTax: 2, + Bundle: "allowed_bundle", + Attr: []adcom1.CreativeAttribute{2}, + ImpID: impID2, + }, }, - }, - }}, + }}, + }, config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true}, "bcat":{"enforce_blocks": true}, "cattax":{"enforce_blocks": true}, "bapp":{"enforce_blocks": true}, "battr":{"enforce_blocks": true}}}`), expectedBids: []*adapters.TypedBid{ { @@ -1511,7 +1564,7 @@ func TestHandleRawBidderResponseHook(t *testing.T) { assert.NoError(t, err) test.payload = newPayload } - assert.Equal(t, test.expectedBids, test.payload.Bids, "Invalid Bids returned after executing RawBidderResponse hook.") + assert.Equal(t, test.expectedBids, test.payload.BidderResponse.Bids, "Invalid Bids returned after executing RawBidderResponse hook.") // reset ChangeSet not to break hookResult assertion, we validated ChangeSet separately hookResult.ChangeSet = hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{} diff --git a/modules/prebid/ortb2blocking/utils.go b/modules/prebid/ortb2blocking/utils.go index 701658dc6cc..a6b9c02c708 100644 --- a/modules/prebid/ortb2blocking/utils.go +++ b/modules/prebid/ortb2blocking/utils.go @@ -3,8 +3,8 @@ package ortb2blocking import ( "strings" - "github.com/prebid/openrtb/v19/adcom1" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/adcom1" + "github.com/prebid/openrtb/v20/openrtb2" ) func mergeStrings(messages []string, newMessages ...string) []string { diff --git a/openrtb_ext/alternatebiddercodes.go b/openrtb_ext/alternatebiddercodes.go index 49291a3e5e8..3dcb6c781fa 100644 --- a/openrtb_ext/alternatebiddercodes.go +++ b/openrtb_ext/alternatebiddercodes.go @@ -1,6 +1,9 @@ package openrtb_ext -import "fmt" +import ( + "fmt" + "strings" +) // ExtAlternateBidderCodes defines list of alternate bidder codes allowed by adatpers. This overrides host level configs. type ExtAlternateBidderCodes struct { @@ -14,7 +17,7 @@ type ExtAdapterAlternateBidderCodes struct { } func (bidderCodes *ExtAlternateBidderCodes) IsValidBidderCode(bidder, alternateBidder string) (bool, error) { - if alternateBidder == "" || bidder == alternateBidder { + if alternateBidder == "" || strings.EqualFold(bidder, alternateBidder) { return true, nil } @@ -26,8 +29,8 @@ func (bidderCodes *ExtAlternateBidderCodes) IsValidBidderCode(bidder, alternateB return false, alternateBidderNotDefinedError(bidder, alternateBidder) } - adapterCfg, ok := bidderCodes.Bidders[bidder] - if !ok { + adapterCfg, found := bidderCodes.IsBidderInAlternateBidderCodes(bidder) + if !found { return false, alternateBidderNotDefinedError(bidder, alternateBidder) } @@ -56,3 +59,23 @@ func alternateBidderDisabledError(bidder, alternateBidder string) error { func alternateBidderNotDefinedError(bidder, alternateBidder string) error { return fmt.Errorf("alternateBidderCodes not defined for adapter %q, rejecting bids for %q", bidder, alternateBidder) } + +// IsBidderInAlternateBidderCodes tries to find bidder in the altBidderCodes.Bidders map in a case sensitive +// manner first. If no match is found it'll try it in a case insensitive way in linear time +func (bidderCodes *ExtAlternateBidderCodes) IsBidderInAlternateBidderCodes(bidder string) (ExtAdapterAlternateBidderCodes, bool) { + if len(bidder) > 0 && bidderCodes != nil && len(bidderCodes.Bidders) > 0 { + // try constant time exact match + if adapterCfg, found := bidderCodes.Bidders[bidder]; found { + return adapterCfg, true + } + + // check if we can find with a case insensitive comparison + for bidderName, adapterCfg := range bidderCodes.Bidders { + if strings.EqualFold(bidder, bidderName) { + return adapterCfg, true + } + } + } + + return ExtAdapterAlternateBidderCodes{}, false +} diff --git a/openrtb_ext/alternatebiddercodes_test.go b/openrtb_ext/alternatebiddercodes_test.go index 438aebad559..f9229b13d2a 100644 --- a/openrtb_ext/alternatebiddercodes_test.go +++ b/openrtb_ext/alternatebiddercodes_test.go @@ -35,6 +35,14 @@ func TestAlternateBidderCodes_IsValidBidderCode(t *testing.T) { }, wantIsValid: true, }, + { + name: "alternateBidder and bidder are the same under Unicode case-folding (default non-extra bid case with seat's alternateBidder explicitly set)", + args: args{ + bidder: "pubmatic", + alternateBidder: "pubmatic", + }, + wantIsValid: true, + }, { name: "account.alternatebiddercodes config not defined (default, reject bid)", args: args{ @@ -98,6 +106,20 @@ func TestAlternateBidderCodes_IsValidBidderCode(t *testing.T) { }, wantIsValid: true, }, + { + name: "bidder is different in casing than the entry in account.alternatebiddercodes but they match because our case insensitive comparison", + args: args{ + bidder: "PUBmatic", + alternateBidder: "groupm", + }, + fields: fields{ + Enabled: true, + Bidders: map[string]ExtAdapterAlternateBidderCodes{ + "pubmatic": {Enabled: true}, + }, + }, + wantIsValid: true, + }, { name: "allowedBidderCodes is *", args: args{ @@ -178,3 +200,129 @@ func TestAlternateBidderCodes_IsValidBidderCode(t *testing.T) { }) } } + +func TestIsBidderInAlternateBidderCodes(t *testing.T) { + type testInput struct { + bidder string + bidderCodes *ExtAlternateBidderCodes + } + type testOutput struct { + adapterCfg ExtAdapterAlternateBidderCodes + found bool + } + testCases := []struct { + desc string + in testInput + expected testOutput + }{ + { + desc: "empty bidder", + in: testInput{ + bidderCodes: &ExtAlternateBidderCodes{}, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{}, + found: false, + }, + }, + { + desc: "nil ExtAlternateBidderCodes", + in: testInput{ + bidder: "appnexus", + bidderCodes: nil, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{}, + found: false, + }, + }, + { + desc: "nil ExtAlternateBidderCodes.Bidder map", + in: testInput{ + bidder: "appnexus", + bidderCodes: &ExtAlternateBidderCodes{}, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{}, + found: false, + }, + }, + { + desc: "nil ExtAlternateBidderCodes.Bidder map", + in: testInput{ + bidder: "appnexus", + bidderCodes: &ExtAlternateBidderCodes{ + Bidders: nil, + }, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{}, + found: false, + }, + }, + { + desc: "bidder arg identical to entry in Bidders map", + in: testInput{ + bidder: "appnexus", + bidderCodes: &ExtAlternateBidderCodes{ + Bidders: map[string]ExtAdapterAlternateBidderCodes{ + "appnexus": { + Enabled: true, + AllowedBidderCodes: []string{"abcCode"}, + }, + }, + }, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{ + Enabled: true, + AllowedBidderCodes: []string{"abcCode"}, + }, + found: true, + }, + }, + { + desc: "bidder arg matches an entry in Bidders map with case insensitive comparisson", + in: testInput{ + bidder: "appnexus", + bidderCodes: &ExtAlternateBidderCodes{ + Bidders: map[string]ExtAdapterAlternateBidderCodes{ + "AppNexus": {AllowedBidderCodes: []string{"adnxsCode"}}, + "PubMatic": {AllowedBidderCodes: []string{"pubCode"}}, + "Rubicon": {AllowedBidderCodes: []string{"rCode"}}, + }, + }, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{ + AllowedBidderCodes: []string{"adnxsCode"}, + }, + found: true, + }, + }, + { + desc: "bidder arg doesn't match any entry in map", + in: testInput{ + bidder: "unknown", + bidderCodes: &ExtAlternateBidderCodes{ + Bidders: map[string]ExtAdapterAlternateBidderCodes{ + "AppNexus": {AllowedBidderCodes: []string{"adnxsCode"}}, + "PubMatic": {AllowedBidderCodes: []string{"pubCode"}}, + "Rubicon": {AllowedBidderCodes: []string{"rCode"}}, + }, + }, + }, + expected: testOutput{ + adapterCfg: ExtAdapterAlternateBidderCodes{}, + found: false, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + adapterCfg, found := tc.in.bidderCodes.IsBidderInAlternateBidderCodes(tc.in.bidder) + assert.Equal(t, tc.expected.adapterCfg, adapterCfg) + assert.Equal(t, tc.expected.found, found) + }) + } +} diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go index 2e190389212..7d3dcbd70bf 100644 --- a/openrtb_ext/bid.go +++ b/openrtb_ext/bid.go @@ -7,6 +7,7 @@ import ( // ExtBid defines the contract for bidresponse.seatbid.bid[i].ext type ExtBid struct { + DSA *ExtBidDSA `json:"dsa,omitempty"` Prebid *ExtBidPrebid `json:"prebid,omitempty"` } @@ -83,6 +84,13 @@ type ExtBidPrebidEvents struct { Imp string `json:"imp,omitempty"` } +// ExtBidDSA defines the contract for bidresponse.seatbid.bid[i].ext.dsa +type ExtBidDSA struct { + AdRender *int8 `json:"adrender,omitempty"` + Behalf string `json:"behalf,omitempty"` + Paid string `json:"paid,omitempty"` +} + // BidType describes the allowed values for bidresponse.seatbid.bid[i].ext.prebid.type type BidType string diff --git a/openrtb_ext/bid_request_video.go b/openrtb_ext/bid_request_video.go index d9505fb6257..fbaf1dbb54c 100644 --- a/openrtb_ext/bid_request_video.go +++ b/openrtb_ext/bid_request_video.go @@ -1,6 +1,6 @@ package openrtb_ext -import "github.com/prebid/openrtb/v19/openrtb2" +import "github.com/prebid/openrtb/v20/openrtb2" type BidRequestVideo struct { // Attribute: diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 5312b5ac4e7..7fe46003597 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -22,12 +22,14 @@ var coreBidderNames []BidderName = []BidderName{ BidderAax, BidderAceex, BidderAcuityAds, + BidderAdelement, BidderAdf, BidderAdgeneration, BidderAdhese, BidderAdkernel, BidderAdkernelAdn, BidderAdman, + BidderAdmatic, BidderAdmixer, BidderAdnuntius, BidderAdOcean, @@ -41,6 +43,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderAdtarget, BidderAdtrgtme, BidderAdtelligent, + BidderAdTonos, BidderAdvangelists, BidderAdView, BidderAdxcg, @@ -48,10 +51,12 @@ var coreBidderNames []BidderName = []BidderName{ BidderAidem, BidderAJA, BidderAlgorix, + BidderAlkimi, BidderAMX, BidderApacdex, BidderAppnexus, BidderAppush, + BidderAso, BidderAudienceNetwork, BidderAutomatad, BidderAvocet, @@ -63,24 +68,30 @@ var coreBidderNames []BidderName = []BidderName{ BidderBetween, BidderBeyondMedia, BidderBidmachine, + BidderBidmatic, BidderBidmyadz, BidderBidsCube, BidderBidstack, - BidderBizzclick, + BidderBigoAd, + BidderBlasto, BidderBliink, BidderBlue, BidderBluesea, BidderBmtm, BidderBoldwin, BidderBrave, + BidderBWX, BidderCadentApertureMX, BidderCcx, + BidderCointraffic, BidderCoinzilla, BidderColossus, BidderCompass, + BidderConcert, BidderConnectAd, BidderConsumable, BidderConversant, + BidderCopper6ssp, BidderCpmstar, BidderCriteo, BidderCWire, @@ -89,18 +100,19 @@ var coreBidderNames []BidderName = []BidderName{ BidderDeepintent, BidderDefinemedia, BidderDianomi, + BidderDisplayio, BidderEdge226, BidderDmx, BidderDXKulture, + BidderDriftPixel, BidderEmtv, BidderEmxDigital, BidderEPlanning, BidderEpom, - BidderEpsilon, + BidderEscalax, BidderEVolution, BidderFlipp, BidderFreewheelSSP, - BidderFreewheelSSPOld, BidderFRVRAdNetwork, BidderGamma, BidderGamoshi, @@ -128,40 +140,54 @@ var coreBidderNames []BidderName = []BidderName{ BidderLmKiviads, BidderKrushmedia, BidderLemmadigital, - BidderLiftoff, BidderLimelightDigital, BidderLockerDome, BidderLogan, BidderLogicad, + BidderLoyal, BidderLunaMedia, BidderMabidder, BidderMadvertise, BidderMarsmedia, BidderMediafuse, + BidderMediaGo, BidderMedianet, + BidderMeloZen, + BidderMetaX, BidderMgid, BidderMgidX, + BidderMinuteMedia, + BidderMissena, BidderMobfoxpb, BidderMobileFuse, BidderMotorik, + BidderNativo, BidderNextMillennium, BidderNoBid, + BidderOms, BidderOneTag, BidderOpenWeb, BidderOpenx, BidderOperaads, + BidderOraki, BidderOrbidder, BidderOutbrain, BidderOwnAdx, BidderPangle, BidderPGAMSsp, + BidderPlaydigo, BidderPubmatic, + BidderPubrise, BidderPubnative, BidderPulsepoint, BidderPWBid, + BidderQT, + BidderReadpeak, + BidderRelevantDigital, BidderRevcontent, BidderRichaudience, BidderRise, + BidderRoulax, BidderRTBHouse, BidderRubicon, BidderSeedingAlliance, @@ -177,25 +203,28 @@ var coreBidderNames []BidderName = []BidderName{ BidderSmartx, BidderSmartyAds, BidderSmileWanted, + BidderSmrtconnect, BidderSonobi, BidderSovrn, + BidderSovrnXsp, BidderSspBC, BidderStroeerCore, - BidderSuntContent, - BidderSynacormedia, BidderTaboola, BidderTappx, BidderTeads, BidderTelaria, + BidderTheadx, + BidderTheTradeDesk, BidderTpmn, BidderTrafficGate, BidderTriplelift, BidderTripleliftNative, - BidderTrustX, + BidderTrustedstack, BidderUcfunnel, BidderUndertone, BidderUnicorn, BidderUnruly, + BidderVidazoo, BidderVideoByte, BidderVideoHeroes, BidderVidoomy, @@ -203,16 +232,17 @@ var coreBidderNames []BidderName = []BidderName{ BidderVisx, BidderVox, BidderVrtcal, + BidderVungle, BidderXeworks, BidderYahooAds, - BidderYahooAdvertising, - BidderYahooSSP, + BidderYandex, BidderYeahmobi, BidderYieldlab, BidderYieldmo, BidderYieldone, BidderZeroClickFraud, BidderZetaGlobalSsp, + BidderZmaticoo, } func GetAliasBidderToParent() map[BidderName]BidderName { @@ -230,10 +260,6 @@ func SetAliasBidderName(aliasBidderName string, parentBidderName BidderName) err return nil } -func (name BidderName) MarshalJSON() ([]byte, error) { - return []byte(name), nil -} - func (name *BidderName) String() string { if name == nil { return "" @@ -251,7 +277,8 @@ const ( BidderReservedPrebid BidderName = "prebid" // Reserved for Prebid Server configuration. BidderReservedSKAdN BidderName = "skadn" // Reserved for Apple's SKAdNetwork OpenRTB extension. BidderReservedTID BidderName = "tid" // Reserved for Per-Impression Transactions IDs for Multi-Impression Bid Requests. - BidderReservedAE BidderName = "ae" // Reserved for FLEDGE Auction Environment + BidderReservedAE BidderName = "ae" // Reserved for PAAPI Auction Environment. + BidderReservedIGS BidderName = "igs" // Reserved for PAAPI Interest Group Seller object. ) // IsBidderNameReserved returns true if the specified name is a case insensitive match for a reserved bidder name. @@ -292,9 +319,37 @@ func IsBidderNameReserved(name string) bool { return true } + if strings.EqualFold(name, string(BidderReservedIGS)) { + return true + } + return false } +// IsPotentialBidder returns true if the name is not reserved within the imp[].ext context +func IsPotentialBidder(name string) bool { + switch BidderName(name) { + case BidderReservedContext: + return false + case BidderReservedData: + return false + case BidderReservedGPID: + return false + case BidderReservedPrebid: + return false + case BidderReservedSKAdN: + return false + case BidderReservedTID: + return false + case BidderReservedAE: + return false + case BidderReservedIGS: + return false + default: + return true + } +} + // Names of core bidders. These names *must* match the bidder code in Prebid.js if an adapter also exists in that // project. You may *not* use the name 'general' as that is reserved for general error messages nor 'context' as // that is reserved for first party data. @@ -305,12 +360,14 @@ const ( BidderAax BidderName = "aax" BidderAceex BidderName = "aceex" BidderAcuityAds BidderName = "acuityads" + BidderAdelement BidderName = "adelement" BidderAdf BidderName = "adf" BidderAdgeneration BidderName = "adgeneration" BidderAdhese BidderName = "adhese" BidderAdkernel BidderName = "adkernel" BidderAdkernelAdn BidderName = "adkernelAdn" BidderAdman BidderName = "adman" + BidderAdmatic BidderName = "admatic" BidderAdmixer BidderName = "admixer" BidderAdnuntius BidderName = "adnuntius" BidderAdOcean BidderName = "adocean" @@ -323,6 +380,7 @@ const ( BidderAdsinteractive BidderName = "adsinteractive" BidderAdtarget BidderName = "adtarget" BidderAdtrgtme BidderName = "adtrgtme" + BidderAdTonos BidderName = "adtonos" BidderAdtelligent BidderName = "adtelligent" BidderAdvangelists BidderName = "advangelists" BidderAdView BidderName = "adview" @@ -331,10 +389,12 @@ const ( BidderAidem BidderName = "aidem" BidderAJA BidderName = "aja" BidderAlgorix BidderName = "algorix" + BidderAlkimi BidderName = "alkimi" BidderAMX BidderName = "amx" BidderApacdex BidderName = "apacdex" BidderAppnexus BidderName = "appnexus" BidderAppush BidderName = "appush" + BidderAso BidderName = "aso" BidderAudienceNetwork BidderName = "audienceNetwork" BidderAutomatad BidderName = "automatad" BidderAvocet BidderName = "avocet" @@ -346,24 +406,30 @@ const ( BidderBetween BidderName = "between" BidderBeyondMedia BidderName = "beyondmedia" BidderBidmachine BidderName = "bidmachine" + BidderBidmatic BidderName = "bidmatic" BidderBidmyadz BidderName = "bidmyadz" BidderBidsCube BidderName = "bidscube" BidderBidstack BidderName = "bidstack" - BidderBizzclick BidderName = "bizzclick" + BidderBigoAd BidderName = "bigoad" + BidderBlasto BidderName = "blasto" BidderBliink BidderName = "bliink" BidderBlue BidderName = "blue" BidderBluesea BidderName = "bluesea" BidderBmtm BidderName = "bmtm" BidderBoldwin BidderName = "boldwin" BidderBrave BidderName = "brave" + BidderBWX BidderName = "bwx" BidderCadentApertureMX BidderName = "cadent_aperture_mx" BidderCcx BidderName = "ccx" + BidderCointraffic BidderName = "cointraffic" BidderCoinzilla BidderName = "coinzilla" BidderColossus BidderName = "colossus" BidderCompass BidderName = "compass" + BidderConcert BidderName = "concert" BidderConnectAd BidderName = "connectad" BidderConsumable BidderName = "consumable" BidderConversant BidderName = "conversant" + BidderCopper6ssp BidderName = "copper6ssp" BidderCpmstar BidderName = "cpmstar" BidderCriteo BidderName = "criteo" BidderCWire BidderName = "cwire" @@ -372,18 +438,19 @@ const ( BidderDeepintent BidderName = "deepintent" BidderDefinemedia BidderName = "definemedia" BidderDianomi BidderName = "dianomi" + BidderDisplayio BidderName = "displayio" BidderEdge226 BidderName = "edge226" BidderDmx BidderName = "dmx" BidderDXKulture BidderName = "dxkulture" + BidderDriftPixel BidderName = "driftpixel" BidderEmtv BidderName = "emtv" BidderEmxDigital BidderName = "emx_digital" BidderEPlanning BidderName = "eplanning" - BidderEpsilon BidderName = "epsilon" BidderEpom BidderName = "epom" + BidderEscalax BidderName = "escalax" BidderEVolution BidderName = "e_volution" BidderFlipp BidderName = "flipp" BidderFreewheelSSP BidderName = "freewheelssp" - BidderFreewheelSSPOld BidderName = "freewheel-ssp" BidderFRVRAdNetwork BidderName = "frvradn" BidderGamma BidderName = "gamma" BidderGamoshi BidderName = "gamoshi" @@ -411,40 +478,54 @@ const ( BidderLmKiviads BidderName = "lm_kiviads" BidderKrushmedia BidderName = "krushmedia" BidderLemmadigital BidderName = "lemmadigital" - BidderLiftoff BidderName = "liftoff" BidderLimelightDigital BidderName = "limelightDigital" BidderLockerDome BidderName = "lockerdome" BidderLogan BidderName = "logan" BidderLogicad BidderName = "logicad" + BidderLoyal BidderName = "loyal" BidderLunaMedia BidderName = "lunamedia" BidderMabidder BidderName = "mabidder" BidderMadvertise BidderName = "madvertise" BidderMarsmedia BidderName = "marsmedia" BidderMediafuse BidderName = "mediafuse" + BidderMediaGo BidderName = "mediago" BidderMedianet BidderName = "medianet" + BidderMeloZen BidderName = "melozen" + BidderMetaX BidderName = "metax" BidderMgid BidderName = "mgid" BidderMgidX BidderName = "mgidX" + BidderMinuteMedia BidderName = "minutemedia" + BidderMissena BidderName = "missena" BidderMobfoxpb BidderName = "mobfoxpb" BidderMobileFuse BidderName = "mobilefuse" BidderMotorik BidderName = "motorik" + BidderNativo BidderName = "nativo" BidderNextMillennium BidderName = "nextmillennium" BidderNoBid BidderName = "nobid" + BidderOms BidderName = "oms" BidderOneTag BidderName = "onetag" BidderOpenWeb BidderName = "openweb" BidderOpenx BidderName = "openx" BidderOperaads BidderName = "operaads" + BidderOraki BidderName = "oraki" BidderOrbidder BidderName = "orbidder" BidderOutbrain BidderName = "outbrain" BidderOwnAdx BidderName = "ownadx" BidderPangle BidderName = "pangle" BidderPGAMSsp BidderName = "pgamssp" + BidderPlaydigo BidderName = "playdigo" BidderPubmatic BidderName = "pubmatic" + BidderPubrise BidderName = "pubrise" BidderPubnative BidderName = "pubnative" BidderPulsepoint BidderName = "pulsepoint" BidderPWBid BidderName = "pwbid" + BidderQT BidderName = "qt" + BidderReadpeak BidderName = "readpeak" + BidderRelevantDigital BidderName = "relevantdigital" BidderRevcontent BidderName = "revcontent" BidderRichaudience BidderName = "richaudience" BidderRise BidderName = "rise" + BidderRoulax BidderName = "roulax" BidderRTBHouse BidderName = "rtbhouse" BidderRubicon BidderName = "rubicon" BidderSeedingAlliance BidderName = "seedingAlliance" @@ -460,25 +541,28 @@ const ( BidderSmartx BidderName = "smartx" BidderSmartyAds BidderName = "smartyads" BidderSmileWanted BidderName = "smilewanted" + BidderSmrtconnect BidderName = "smrtconnect" BidderSonobi BidderName = "sonobi" BidderSovrn BidderName = "sovrn" + BidderSovrnXsp BidderName = "sovrnXsp" BidderSspBC BidderName = "sspBC" BidderStroeerCore BidderName = "stroeerCore" - BidderSuntContent BidderName = "suntContent" - BidderSynacormedia BidderName = "synacormedia" BidderTaboola BidderName = "taboola" BidderTappx BidderName = "tappx" BidderTeads BidderName = "teads" BidderTelaria BidderName = "telaria" + BidderTheadx BidderName = "theadx" + BidderTheTradeDesk BidderName = "thetradedesk" BidderTpmn BidderName = "tpmn" BidderTrafficGate BidderName = "trafficgate" BidderTriplelift BidderName = "triplelift" BidderTripleliftNative BidderName = "triplelift_native" - BidderTrustX BidderName = "trustx" + BidderTrustedstack BidderName = "trustedstack" BidderUcfunnel BidderName = "ucfunnel" BidderUndertone BidderName = "undertone" BidderUnicorn BidderName = "unicorn" BidderUnruly BidderName = "unruly" + BidderVidazoo BidderName = "vidazoo" BidderVideoByte BidderName = "videobyte" BidderVideoHeroes BidderName = "videoheroes" BidderVidoomy BidderName = "vidoomy" @@ -486,16 +570,17 @@ const ( BidderVisx BidderName = "visx" BidderVox BidderName = "vox" BidderVrtcal BidderName = "vrtcal" + BidderVungle BidderName = "vungle" BidderXeworks BidderName = "xeworks" BidderYahooAds BidderName = "yahooAds" - BidderYahooAdvertising BidderName = "yahooAdvertising" - BidderYahooSSP BidderName = "yahoossp" + BidderYandex BidderName = "yandex" BidderYeahmobi BidderName = "yeahmobi" BidderYieldlab BidderName = "yieldlab" BidderYieldmo BidderName = "yieldmo" BidderYieldone BidderName = "yieldone" BidderZeroClickFraud BidderName = "zeroclickfraud" BidderZetaGlobalSsp BidderName = "zeta_global_ssp" + BidderZmaticoo BidderName = "zmaticoo" ) // CoreBidderNames returns a slice of all core bidders. @@ -541,6 +626,8 @@ var bidderNameLookup = func() map[string]BidderName { return lookup }() +type BidderNameNormalizer func(name string) (BidderName, bool) + func NormalizeBidderName(name string) (BidderName, bool) { nameLower := strings.ToLower(name) bidderName, exists := bidderNameLookup[nameLower] @@ -614,6 +701,7 @@ func NewBidderParamsValidator(schemaDirectory string) (BidderParamValidator, err if _, ok := bidderMap[bidderName]; !ok { return nil, fmt.Errorf("File %s/%s does not match a valid BidderName.", schemaDirectory, fileInfo.Name()) } + toOpen, err := paramsValidator.abs(filepath.Join(schemaDirectory, fileInfo.Name())) if err != nil { return nil, fmt.Errorf("Failed to get an absolute representation of the path: %s, %v", toOpen, err) @@ -633,7 +721,7 @@ func NewBidderParamsValidator(schemaDirectory string) (BidderParamValidator, err schemaContents[BidderName(bidderName)] = string(fileBytes) } - //set alias bidder params schema to its parent + // set alias bidder params schema to its parent for alias, parent := range aliasBidderToParent { parentSchema := schemas[parent] schemas[alias] = parentSchema diff --git a/openrtb_ext/bidders_validate_test.go b/openrtb_ext/bidders_validate_test.go index 33148d1ed41..66de818311d 100644 --- a/openrtb_ext/bidders_validate_test.go +++ b/openrtb_ext/bidders_validate_test.go @@ -50,7 +50,7 @@ func TestBidderUniquenessGatekeeping(t *testing.T) { // - Exclude duplicates of adapters for the same bidder, as it's unlikely a publisher will use both. var bidders []string for _, bidder := range CoreBidderNames() { - if bidder != BidderSilverPush && bidder != BidderTripleliftNative && bidder != BidderAdkernelAdn && bidder != BidderFreewheelSSPOld && bidder != BidderYahooAdvertising { + if bidder != BidderSilverPush && bidder != BidderTripleliftNative && bidder != BidderAdkernelAdn && bidder != "freewheel-ssp" && bidder != "yahooAdvertising" { bidders = append(bidders, string(bidder)) } } diff --git a/openrtb_ext/convert_down.go b/openrtb_ext/convert_down.go index 573ef64fdc0..bfb6028d8c7 100644 --- a/openrtb_ext/convert_down.go +++ b/openrtb_ext/convert_down.go @@ -31,12 +31,6 @@ func ConvertDownTo25(r *RequestWrapper) error { } } - // Remove fields introduced in OpenRTB 2.6+. The previous OpenRTB 2.5 spec did not specify that - // bidders must tolerate new or unexpected fields. - clear26Fields(r) - clear202211Fields(r) - clear202303Fields(r) - return nil } @@ -308,3 +302,27 @@ func clear202303Fields(r *RequestWrapper) { } } } + +// clear202309Fields sets all fields introduced in OpenRTB 2.6-202309 to default values +// which will cause them to be omitted during json marshal. +func clear202309Fields(r *RequestWrapper) { + r.ACat = nil + + for _, imp := range r.GetImp() { + if audio := imp.Audio; audio != nil { + audio.DurFloors = nil + } + + if video := imp.Video; video != nil { + video.DurFloors = nil + } + + if pmp := imp.PMP; pmp != nil { + for i := range pmp.Deals { + pmp.Deals[i].Guar = 0 + pmp.Deals[i].MinCPMPerSec = 0 + pmp.Deals[i].DurFloors = nil + } + } + } +} diff --git a/openrtb_ext/convert_down_test.go b/openrtb_ext/convert_down_test.go index 3b78337df8b..f32f3da04bb 100644 --- a/openrtb_ext/convert_down_test.go +++ b/openrtb_ext/convert_down_test.go @@ -4,9 +4,10 @@ import ( "encoding/json" "testing" - "github.com/prebid/openrtb/v19/adcom1" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/openrtb/v20/adcom1" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -34,40 +35,6 @@ func TestConvertDownTo25(t *testing.T) { User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}]}`)}, }, }, - { - name: "2.6-dropped", // integration with clear26Fields - givenRequest: openrtb2.BidRequest{ - ID: "anyID", - CatTax: adcom1.CatTaxIABContent10, - Device: &openrtb2.Device{LangB: "anyLang"}, - }, - expectedRequest: openrtb2.BidRequest{ - ID: "anyID", - Device: &openrtb2.Device{}, - }, - }, - { - name: "2.6-202211-dropped", // integration with clear202211Fields - givenRequest: openrtb2.BidRequest{ - ID: "anyID", - App: &openrtb2.App{InventoryPartnerDomain: "anyDomain"}, - }, - expectedRequest: openrtb2.BidRequest{ - ID: "anyID", - App: &openrtb2.App{}, - }, - }, - { - name: "2.6-202303-dropped", // integration with clear202303Fields - givenRequest: openrtb2.BidRequest{ - ID: "anyID", - Imp: []openrtb2.Imp{{ID: "1", Refresh: &openrtb2.Refresh{Count: 1}}}, - }, - expectedRequest: openrtb2.BidRequest{ - ID: "anyID", - Imp: []openrtb2.Imp{{ID: "1"}}, - }, - }, { name: "2.6-to-2.5-OtherExtFields", givenRequest: openrtb2.BidRequest{ @@ -683,7 +650,7 @@ func TestClear202303Fields(t *testing.T) { { ID: "imp1", Video: &openrtb2.Video{PodID: "1", Plcmt: adcom1.VideoPlcmtInstream}, - Refresh: &openrtb2.Refresh{Count: 1}, + Refresh: &openrtb2.Refresh{Count: ptrutil.ToPtr(1)}, }, }, } @@ -702,3 +669,50 @@ func TestClear202303Fields(t *testing.T) { clear202303Fields(r) assert.Equal(t, expected, given) } + +func TestClear202309Fields(t *testing.T) { + givenDurFloors := []openrtb2.DurFloors{{MinDur: 15, MaxDur: 30, BidFloor: 100}} + + given := openrtb2.BidRequest{ + ID: "anyID", + ACat: []string{"acat1", "acat2"}, + Imp: []openrtb2.Imp{ + { + ID: "imp1", + Audio: &openrtb2.Audio{PodID: "1", DurFloors: givenDurFloors}, + }, + { + ID: "imp2", + Video: &openrtb2.Video{PodID: "2", DurFloors: givenDurFloors}, + PMP: &openrtb2.PMP{ + PrivateAuction: 1, + Deals: []openrtb2.Deal{ + {ID: "deal1", BidFloor: 200, Guar: 1, MinCPMPerSec: 2, DurFloors: givenDurFloors}}, + }, + }, + }, + } + + expected := openrtb2.BidRequest{ + ID: "anyID", + Imp: []openrtb2.Imp{ + { + ID: "imp1", + Audio: &openrtb2.Audio{PodID: "1"}, + }, + { + ID: "imp2", + Video: &openrtb2.Video{PodID: "2"}, + PMP: &openrtb2.PMP{ + PrivateAuction: 1, + Deals: []openrtb2.Deal{ + {ID: "deal1", BidFloor: 200}}, + }, + }, + }, + } + + r := &RequestWrapper{BidRequest: &given} + clear202309Fields(r) + assert.Equal(t, expected, given) +} diff --git a/openrtb_ext/convert_up.go b/openrtb_ext/convert_up.go index e8116cad11c..cfc9259b206 100644 --- a/openrtb_ext/convert_up.go +++ b/openrtb_ext/convert_up.go @@ -3,7 +3,7 @@ package openrtb_ext import ( "fmt" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" ) func ConvertUpTo26(r *RequestWrapper) error { @@ -168,7 +168,7 @@ func moveRewardedFromPrebidExtTo26(i *ImpWrapper) { // read and clear prebid ext impExt, _ := i.GetImpExt() rwddPrebidExt := (*int8)(nil) - if p := impExt.GetPrebid(); p != nil { + if p := impExt.GetPrebid(); p != nil && p.IsRewardedInventory != nil { rwddPrebidExt = p.IsRewardedInventory p.IsRewardedInventory = nil impExt.SetPrebid(p) diff --git a/openrtb_ext/convert_up_test.go b/openrtb_ext/convert_up_test.go index 3cafe8c1612..2b5c2303897 100644 --- a/openrtb_ext/convert_up_test.go +++ b/openrtb_ext/convert_up_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" "github.com/stretchr/testify/assert" ) @@ -417,12 +417,12 @@ func TestMoveRewardedFromPrebidExtTo26(t *testing.T) { { description: "Not Present - Null Prebid Ext", givenImp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":null}`)}, - expectedImp: openrtb2.Imp{}, // empty prebid object pruned by RebuildImp + expectedImp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":null}`)}, }, { description: "Not Present - Empty Prebid Ext", givenImp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{}}`)}, - expectedImp: openrtb2.Imp{}, // empty prebid object pruned by RebuildImp + expectedImp: openrtb2.Imp{Ext: json.RawMessage(`{"prebid":{}}`)}, }, { description: "Prebid Ext Migrated To 2.6", diff --git a/openrtb_ext/deal_tier.go b/openrtb_ext/deal_tier.go index df386916b77..b0f2675d660 100644 --- a/openrtb_ext/deal_tier.go +++ b/openrtb_ext/deal_tier.go @@ -1,8 +1,8 @@ package openrtb_ext import ( - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) // DealTier defines the configuration of a deal tier. @@ -38,7 +38,11 @@ func ReadDealTiersFromImp(imp openrtb2.Imp) (DealTierBidderMap, error) { } for bidder, param := range impPrebidExt.Prebid.Bidders { if param.DealTier != nil { - dealTiers[BidderName(bidder)] = *param.DealTier + if bidderNormalized, bidderFound := NormalizeBidderName(bidder); bidderFound { + dealTiers[bidderNormalized] = *param.DealTier + } else { + dealTiers[BidderName(bidder)] = *param.DealTier + } } } diff --git a/openrtb_ext/deal_tier_test.go b/openrtb_ext/deal_tier_test.go index dabecf6a9e7..6808f90a0da 100644 --- a/openrtb_ext/deal_tier_test.go +++ b/openrtb_ext/deal_tier_test.go @@ -4,8 +4,8 @@ import ( "encoding/json" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/errortypes" "github.com/stretchr/testify/assert" ) @@ -17,78 +17,105 @@ func TestReadDealTiersFromImp(t *testing.T) { expectedErrorType error }{ { - description: "Nil", + description: "nil", impExt: nil, expectedResult: DealTierBidderMap{}, }, { - description: "None", + description: "none", impExt: json.RawMessage(``), expectedResult: DealTierBidderMap{}, }, { - description: "Empty Object", + description: "empty_object", impExt: json.RawMessage(`{}`), expectedResult: DealTierBidderMap{}, }, { - description: "imp.ext - no prebid but with other params", + description: "imp.ext_no_prebid_but_with_other_params", impExt: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "anyPrefix"}, "placementId": 12345}, "tid": "1234"}`), expectedResult: DealTierBidderMap{}, }, { - description: "imp.ext.prebid - nil", + description: "imp.ext.prebid_nil", impExt: json.RawMessage(`{"prebid": null}`), expectedResult: DealTierBidderMap{}, }, { - description: "imp.ext.prebid - empty", + description: "imp.ext.prebid_empty", impExt: json.RawMessage(`{"prebid": {}}`), expectedResult: DealTierBidderMap{}, }, { - description: "imp.ext.prebid - no bidder but with other params", + description: "imp.ext.prebid_no bidder but with other params", impExt: json.RawMessage(`{"prebid": {"supportdeals": true}}`), expectedResult: DealTierBidderMap{}, }, { - description: "imp.ext.prebid.bidder - one", + description: "imp.ext.prebid.bidder_one", impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "anyPrefix"}, "placementId": 12345}}}}`), expectedResult: DealTierBidderMap{BidderAppnexus: {Prefix: "anyPrefix", MinDealTier: 5}}, }, { - description: "imp.ext.prebid.bidder - one with other params", + description: "imp.ext.prebid.bidder_one_but_not_found_in_the_adapter_bidder_list", + impExt: json.RawMessage(`{"prebid": {"bidder": {"unknown": {"dealTier": {"minDealTier": 5, "prefix": "anyPrefix"}, "placementId": 12345}}}}`), + expectedResult: DealTierBidderMap{"unknown": {Prefix: "anyPrefix", MinDealTier: 5}}, + }, + { + description: "imp.ext.prebid.bidder_one_but_not_found_in_the_adapter_bidder_list_with_case_insensitive", + impExt: json.RawMessage(`{"prebid": {"bidder": {"UnKnOwn": {"dealTier": {"minDealTier": 5, "prefix": "anyPrefix"}, "placementId": 12345}}}}`), + expectedResult: DealTierBidderMap{"UnKnOwn": {Prefix: "anyPrefix", MinDealTier: 5}}, + }, + { + description: "imp.ext.prebid.bidder_one_but_case_is_different_from_the_adapter_bidder_list", + impExt: json.RawMessage(`{"prebid": {"bidder": {"APpNExUS": {"dealTier": {"minDealTier": 5, "prefix": "anyPrefix"}, "placementId": 12345}}}}`), + expectedResult: DealTierBidderMap{BidderAppnexus: {Prefix: "anyPrefix", MinDealTier: 5}}, + }, + { + description: "imp.ext.prebid.bidder_one_with_other_params", impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "anyPrefix"}, "placementId": 12345}}, "supportdeals": true}, "tid": "1234"}`), expectedResult: DealTierBidderMap{BidderAppnexus: {Prefix: "anyPrefix", MinDealTier: 5}}, }, { - description: "imp.ext.prebid.bidder - multiple", + description: "imp.ext.prebid.bidder_multiple", impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "appnexusPrefix"}, "placementId": 12345}, "rubicon": {"dealTier": {"minDealTier": 8, "prefix": "rubiconPrefix"}, "placementId": 12345}}}}`), expectedResult: DealTierBidderMap{BidderAppnexus: {Prefix: "appnexusPrefix", MinDealTier: 5}, BidderRubicon: {Prefix: "rubiconPrefix", MinDealTier: 8}}, }, { - description: "imp.ext.prebid.bidder - one without deal tier", + description: "imp.ext.prebid.bidder_one_without_deal_tier", impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"placementId": 12345}}}}`), expectedResult: DealTierBidderMap{}, }, { - description: "imp.ext.prebid.bidder - error", + description: "imp.ext.prebid.bidder_error", impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": "wrong type", "placementId": 12345}}}}`), expectedErrorType: &errortypes.FailedToUnmarshal{}, }, } for _, test := range testCases { - imp := openrtb2.Imp{Ext: test.impExt} + t.Run(test.description, func(t *testing.T) { - result, err := ReadDealTiersFromImp(imp) + imp := openrtb2.Imp{Ext: test.impExt} + result, err := ReadDealTiersFromImp(imp) - assert.Equal(t, test.expectedResult, result, test.description+":result") + assert.Equal(t, test.expectedResult, result) - if test.expectedErrorType != nil { - assert.IsType(t, test.expectedErrorType, err) - } else { - assert.NoError(t, err, test.description+":error") - } + if test.expectedErrorType != nil { + assert.IsType(t, test.expectedErrorType, err) + } else { + assert.NoError(t, err) + } + }) } + + t.Run("imp.ext.prebid.bidder_dedupe", func(t *testing.T) { + impExt := json.RawMessage(`{"prebid": {"bidder": {"APPNEXUS": {"dealTier": {"minDealTier": 100}},"APpNExUS": {"dealTier": {"minDealTier": 5}}}}}`) + imp := openrtb2.Imp{Ext: impExt} + result, err := ReadDealTiersFromImp(imp) + + assert.Len(t, result, 1) + assert.NotNil(t, result["appnexus"]) + assert.NoError(t, err) + }) } diff --git a/openrtb_ext/device.go b/openrtb_ext/device.go index 8c5b36733b9..2107a3081ff 100644 --- a/openrtb_ext/device.go +++ b/openrtb_ext/device.go @@ -6,7 +6,7 @@ import ( "strconv" "github.com/buger/jsonparser" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v3/errortypes" ) // PrebidExtKey represents the prebid extension key used in requests diff --git a/openrtb_ext/device_test.go b/openrtb_ext/device_test.go index 86a0e1d7ff2..52b679fda38 100644 --- a/openrtb_ext/device_test.go +++ b/openrtb_ext/device_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/openrtb_ext/floors.go b/openrtb_ext/floors.go index 3553946cc2e..1faa6292320 100644 --- a/openrtb_ext/floors.go +++ b/openrtb_ext/floors.go @@ -1,5 +1,12 @@ package openrtb_ext +import ( + "maps" + "slices" + + "github.com/prebid/prebid-server/v3/util/ptrutil" +) + // Defines strings for FetchStatus const ( FetchSuccess = "success" @@ -78,10 +85,11 @@ type PriceFloorEndpoint struct { type PriceFloorData struct { Currency string `json:"currency,omitempty"` SkipRate int `json:"skiprate,omitempty"` - FloorsSchemaVersion string `json:"floorsschemaversion,omitempty"` + FloorsSchemaVersion int `json:"floorsschemaversion,omitempty"` ModelTimestamp int `json:"modeltimestamp,omitempty"` ModelGroups []PriceFloorModelGroup `json:"modelgroups,omitempty"` FloorProvider string `json:"floorprovider,omitempty"` + UseFetchDataRate *int `json:"usefetchdatarate,omitempty"` } type PriceFloorModelGroup struct { @@ -145,3 +153,59 @@ type ExtImp struct { type ImpExtPrebid struct { Floors Price `json:"floors,omitempty"` } + +func (pf *PriceFloorRules) DeepCopy() *PriceFloorRules { + if pf == nil { + return nil + } + + newRules := *pf + newRules.Enabled = ptrutil.Clone(pf.Enabled) + newRules.Skipped = ptrutil.Clone(pf.Skipped) + newRules.Location = ptrutil.Clone(pf.Location) + newRules.Data = pf.Data.DeepCopy() + newRules.Enforcement = pf.Enforcement.DeepCopy() + + return &newRules +} + +func (data *PriceFloorData) DeepCopy() *PriceFloorData { + if data == nil { + return nil + } + + newData := *data + newModelGroups := make([]PriceFloorModelGroup, len(data.ModelGroups)) + + for i := range data.ModelGroups { + var eachGroup PriceFloorModelGroup + eachGroup.Currency = data.ModelGroups[i].Currency + eachGroup.ModelWeight = ptrutil.Clone(data.ModelGroups[i].ModelWeight) + eachGroup.ModelVersion = data.ModelGroups[i].ModelVersion + eachGroup.SkipRate = data.ModelGroups[i].SkipRate + eachGroup.Values = maps.Clone(data.ModelGroups[i].Values) + eachGroup.Default = data.ModelGroups[i].Default + eachGroup.Schema = PriceFloorSchema{ + Fields: slices.Clone(data.ModelGroups[i].Schema.Fields), + Delimiter: data.ModelGroups[i].Schema.Delimiter, + } + newModelGroups[i] = eachGroup + } + newData.ModelGroups = newModelGroups + + return &newData +} + +func (enforcement *PriceFloorEnforcement) DeepCopy() *PriceFloorEnforcement { + if enforcement == nil { + return nil + } + + newEnforcement := *enforcement + newEnforcement.EnforceJS = ptrutil.Clone(enforcement.EnforceJS) + newEnforcement.EnforcePBS = ptrutil.Clone(enforcement.EnforcePBS) + newEnforcement.FloorDeals = ptrutil.Clone(enforcement.FloorDeals) + newEnforcement.BidAdjustment = ptrutil.Clone(enforcement.BidAdjustment) + + return &newEnforcement +} diff --git a/openrtb_ext/floors_test.go b/openrtb_ext/floors_test.go index 20247736768..df7d1e5dc22 100644 --- a/openrtb_ext/floors_test.go +++ b/openrtb_ext/floors_test.go @@ -1,8 +1,10 @@ package openrtb_ext import ( + "reflect" "testing" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -218,3 +220,263 @@ func TestPriceFloorRulesGetEnabled(t *testing.T) { }) } } + +func TestPriceFloorRulesDeepCopy(t *testing.T) { + type fields struct { + FloorMin float64 + FloorMinCur string + SkipRate int + Location *PriceFloorEndpoint + Data *PriceFloorData + Enforcement *PriceFloorEnforcement + Enabled *bool + Skipped *bool + FloorProvider string + FetchStatus string + PriceFloorLocation string + } + tests := []struct { + name string + fields fields + }{ + { + name: "DeepCopy does not share same reference", + fields: fields{ + FloorMin: 10, + FloorMinCur: "INR", + SkipRate: 0, + Location: &PriceFloorEndpoint{ + URL: "https://test/floors", + }, + Data: &PriceFloorData{ + Currency: "INR", + SkipRate: 0, + ModelGroups: []PriceFloorModelGroup{ + { + Currency: "INR", + ModelWeight: ptrutil.ToPtr(1), + SkipRate: 0, + Values: map[string]float64{ + "banner|300x600|www.website5.com": 20, + "*|*|*": 50, + }, + Schema: PriceFloorSchema{ + Fields: []string{"mediaType", "size", "domain"}, + Delimiter: "|", + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pf := &PriceFloorRules{ + FloorMin: tt.fields.FloorMin, + FloorMinCur: tt.fields.FloorMinCur, + SkipRate: tt.fields.SkipRate, + Location: tt.fields.Location, + Data: tt.fields.Data, + Enforcement: tt.fields.Enforcement, + Enabled: tt.fields.Enabled, + Skipped: tt.fields.Skipped, + FloorProvider: tt.fields.FloorProvider, + FetchStatus: tt.fields.FetchStatus, + PriceFloorLocation: tt.fields.PriceFloorLocation, + } + got := pf.DeepCopy() + if got == pf { + t.Errorf("Rules reference are same") + } + if got.Data == pf.Data { + t.Errorf("Floor data reference is same") + } + }) + } +} + +func TestFloorRulesDeepCopy(t *testing.T) { + type fields struct { + FloorMin float64 + FloorMinCur string + SkipRate int + Location *PriceFloorEndpoint + Data *PriceFloorData + Enforcement *PriceFloorEnforcement + Enabled *bool + Skipped *bool + FloorProvider string + FetchStatus string + PriceFloorLocation string + } + tests := []struct { + name string + fields fields + want *PriceFloorRules + }{ + { + name: "Copy entire floors object", + fields: fields{ + FloorMin: 10, + FloorMinCur: "INR", + SkipRate: 0, + Location: &PriceFloorEndpoint{ + URL: "http://prebid.com/floor", + }, + Data: &PriceFloorData{ + Currency: "INR", + SkipRate: 0, + FloorsSchemaVersion: 2, + ModelTimestamp: 123, + ModelGroups: []PriceFloorModelGroup{ + { + Currency: "INR", + ModelWeight: ptrutil.ToPtr(50), + ModelVersion: "version 1", + SkipRate: 0, + Schema: PriceFloorSchema{ + Fields: []string{"a", "b", "c"}, + Delimiter: "|", + }, + Values: map[string]float64{ + "*|*|*": 20, + }, + Default: 1, + }, + }, + FloorProvider: "prebid", + }, + Enforcement: &PriceFloorEnforcement{ + EnforceJS: ptrutil.ToPtr(true), + EnforcePBS: ptrutil.ToPtr(true), + FloorDeals: ptrutil.ToPtr(true), + BidAdjustment: ptrutil.ToPtr(true), + EnforceRate: 100, + }, + Enabled: ptrutil.ToPtr(true), + Skipped: ptrutil.ToPtr(false), + FloorProvider: "Prebid", + FetchStatus: "success", + PriceFloorLocation: "fetch", + }, + want: &PriceFloorRules{ + FloorMin: 10, + FloorMinCur: "INR", + SkipRate: 0, + Location: &PriceFloorEndpoint{ + URL: "http://prebid.com/floor", + }, + Data: &PriceFloorData{ + Currency: "INR", + SkipRate: 0, + FloorsSchemaVersion: 2, + ModelTimestamp: 123, + ModelGroups: []PriceFloorModelGroup{ + { + Currency: "INR", + ModelWeight: ptrutil.ToPtr(50), + ModelVersion: "version 1", + SkipRate: 0, + Schema: PriceFloorSchema{ + Fields: []string{"a", "b", "c"}, + Delimiter: "|", + }, + Values: map[string]float64{ + "*|*|*": 20, + }, + Default: 1, + }, + }, + FloorProvider: "prebid", + }, + Enforcement: &PriceFloorEnforcement{ + EnforceJS: ptrutil.ToPtr(true), + EnforcePBS: ptrutil.ToPtr(true), + FloorDeals: ptrutil.ToPtr(true), + BidAdjustment: ptrutil.ToPtr(true), + EnforceRate: 100, + }, + Enabled: ptrutil.ToPtr(true), + Skipped: ptrutil.ToPtr(false), + FloorProvider: "Prebid", + FetchStatus: "success", + PriceFloorLocation: "fetch", + }, + }, + { + name: "Copy entire floors object", + fields: fields{ + FloorMin: 10, + FloorMinCur: "INR", + SkipRate: 0, + Location: &PriceFloorEndpoint{ + URL: "http://prebid.com/floor", + }, + Data: nil, + Enforcement: &PriceFloorEnforcement{ + EnforceJS: ptrutil.ToPtr(true), + EnforcePBS: ptrutil.ToPtr(true), + FloorDeals: ptrutil.ToPtr(true), + BidAdjustment: ptrutil.ToPtr(true), + EnforceRate: 100, + }, + Enabled: ptrutil.ToPtr(true), + Skipped: ptrutil.ToPtr(false), + FloorProvider: "Prebid", + FetchStatus: "success", + PriceFloorLocation: "fetch", + }, + want: &PriceFloorRules{ + FloorMin: 10, + FloorMinCur: "INR", + SkipRate: 0, + Location: &PriceFloorEndpoint{ + URL: "http://prebid.com/floor", + }, + Data: nil, + Enforcement: &PriceFloorEnforcement{ + EnforceJS: ptrutil.ToPtr(true), + EnforcePBS: ptrutil.ToPtr(true), + FloorDeals: ptrutil.ToPtr(true), + BidAdjustment: ptrutil.ToPtr(true), + EnforceRate: 100, + }, + Enabled: ptrutil.ToPtr(true), + Skipped: ptrutil.ToPtr(false), + FloorProvider: "Prebid", + FetchStatus: "success", + PriceFloorLocation: "fetch", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pf := &PriceFloorRules{ + FloorMin: tt.fields.FloorMin, + FloorMinCur: tt.fields.FloorMinCur, + SkipRate: tt.fields.SkipRate, + Location: tt.fields.Location, + Data: tt.fields.Data, + Enforcement: tt.fields.Enforcement, + Enabled: tt.fields.Enabled, + Skipped: tt.fields.Skipped, + FloorProvider: tt.fields.FloorProvider, + FetchStatus: tt.fields.FetchStatus, + PriceFloorLocation: tt.fields.PriceFloorLocation, + } + if got := pf.DeepCopy(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("PriceFloorRules.DeepCopy() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFloorRuleDeepCopyNil(t *testing.T) { + var priceFloorRule *PriceFloorRules + got := priceFloorRule.DeepCopy() + + if got != nil { + t.Errorf("PriceFloorRules.DeepCopy() = %v, want %v", got, nil) + } +} diff --git a/openrtb_ext/imp.go b/openrtb_ext/imp.go index d0c07ef4e77..85be3124548 100644 --- a/openrtb_ext/imp.go +++ b/openrtb_ext/imp.go @@ -2,6 +2,8 @@ package openrtb_ext import ( "encoding/json" + + "github.com/prebid/openrtb/v20/openrtb2" ) // AuctionEnvironmentType is a Google Privacy Sandbox flag indicating where the auction may take place @@ -41,9 +43,14 @@ type ExtImpPrebid struct { Options *Options `json:"options,omitempty"` + AdUnitCode string `json:"adunitcode,omitempty"` + Passthrough json.RawMessage `json:"passthrough,omitempty"` Floors *ExtImpPrebidFloors `json:"floors,omitempty"` + + // Imp specifies any imp bidder-specific first party data + Imp map[string]json.RawMessage `json:"imp,omitempty"` } type ExtImpDataAdServer struct { @@ -84,3 +91,12 @@ type ExtStoredBidResponse struct { type Options struct { EchoVideoAttrs bool `json:"echovideoattrs"` } + +// GetImpIDs returns slice of all impression Ids from impList +func GetImpIDs(imps []openrtb2.Imp) []string { + impIDs := make([]string, len(imps)) + for i := range imps { + impIDs[i] = imps[i].ID + } + return impIDs +} diff --git a/openrtb_ext/imp_adelement.go b/openrtb_ext/imp_adelement.go new file mode 100644 index 00000000000..7f5316c943f --- /dev/null +++ b/openrtb_ext/imp_adelement.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtAdelement struct { + SupplyId string `json:"supply_id"` +} diff --git a/openrtb_ext/imp_admatic.go b/openrtb_ext/imp_admatic.go new file mode 100644 index 00000000000..89e9acc1f72 --- /dev/null +++ b/openrtb_ext/imp_admatic.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtAdmatic struct { + Host string `json:"host"` + NetworkId int `json:"networkId"` +} diff --git a/openrtb_ext/imp_adtarget.go b/openrtb_ext/imp_adtarget.go index ab6cb5642c6..aa3b6136e58 100644 --- a/openrtb_ext/imp_adtarget.go +++ b/openrtb_ext/imp_adtarget.go @@ -1,9 +1,11 @@ package openrtb_ext +import "encoding/json" + // ExtImpAdtarget defines the contract for bidrequest.imp[i].ext.prebid.bidder.adtarget type ExtImpAdtarget struct { - SourceId int `json:"aid"` - PlacementId int `json:"placementId,omitempty"` - SiteId int `json:"siteId,omitempty"` - BidFloor float64 `json:"bidFloor,omitempty"` + SourceId json.Number `json:"aid"` + PlacementId int `json:"placementId,omitempty"` + SiteId int `json:"siteId,omitempty"` + BidFloor float64 `json:"bidFloor,omitempty"` } diff --git a/openrtb_ext/imp_adtelligent.go b/openrtb_ext/imp_adtelligent.go index c2233209352..b13c44ea360 100644 --- a/openrtb_ext/imp_adtelligent.go +++ b/openrtb_ext/imp_adtelligent.go @@ -1,9 +1,11 @@ package openrtb_ext +import "encoding/json" + // ExtImpAdtelligent defines the contract for bidrequest.imp[i].ext.prebid.bidder.adtelligent type ExtImpAdtelligent struct { - SourceId int `json:"aid"` - PlacementId int `json:"placementId,omitempty"` - SiteId int `json:"siteId,omitempty"` - BidFloor float64 `json:"bidFloor,omitempty"` + SourceId json.Number `json:"aid"` + PlacementId int `json:"placementId,omitempty"` + SiteId int `json:"siteId,omitempty"` + BidFloor float64 `json:"bidFloor,omitempty"` } diff --git a/openrtb_ext/imp_adtonos.go b/openrtb_ext/imp_adtonos.go new file mode 100644 index 00000000000..f59ee35b329 --- /dev/null +++ b/openrtb_ext/imp_adtonos.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ImpExtAdTonos struct { + SupplierID string `json:"supplierId"` +} diff --git a/openrtb_ext/imp_aidem.go b/openrtb_ext/imp_aidem.go index 59457f1eb4a..3fe00b404ff 100644 --- a/openrtb_ext/imp_aidem.go +++ b/openrtb_ext/imp_aidem.go @@ -1,8 +1,8 @@ package openrtb_ext -type ImpExtFoo struct { - SiteID string `json:"siteId"` - PublisherID string `json:"publisherId"` - PlacementID string `json:"placementId"` +type ExtImpAidem struct { + PlacementId string `json:"placementId"` + SiteId string `json:"siteId"` + PublisherId string `json:"publisherId"` RateLimit string `json:"rateLimit"` } diff --git a/openrtb_ext/imp_alkimi.go b/openrtb_ext/imp_alkimi.go new file mode 100644 index 00000000000..640528f212f --- /dev/null +++ b/openrtb_ext/imp_alkimi.go @@ -0,0 +1,9 @@ +package openrtb_ext + +type ExtImpAlkimi struct { + Token string `json:"token"` + BidFloor float64 `json:"bidFloor"` + Instl int8 `json:"instl"` + Exp int64 `json:"exp"` + AdUnitCode string `json:"adUnitCode"` +} diff --git a/openrtb_ext/imp_appnexus.go b/openrtb_ext/imp_appnexus.go index 02476d6bcf0..9e7a43495c8 100644 --- a/openrtb_ext/imp_appnexus.go +++ b/openrtb_ext/imp_appnexus.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) // ExtImpAppnexus defines the contract for bidrequest.imp[i].ext.prebid.bidder.appnexus diff --git a/openrtb_ext/imp_appnexus_test.go b/openrtb_ext/imp_appnexus_test.go index a226c0d8410..52e42d4d53a 100644 --- a/openrtb_ext/imp_appnexus_test.go +++ b/openrtb_ext/imp_appnexus_test.go @@ -3,7 +3,7 @@ package openrtb_ext import ( "testing" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/openrtb_ext/imp_aso.go b/openrtb_ext/imp_aso.go new file mode 100644 index 00000000000..b06a5154b8e --- /dev/null +++ b/openrtb_ext/imp_aso.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpAso struct { + Zone int `json:"zone"` +} diff --git a/openrtb_ext/imp_bidmatic.go b/openrtb_ext/imp_bidmatic.go new file mode 100644 index 00000000000..935c977e7ac --- /dev/null +++ b/openrtb_ext/imp_bidmatic.go @@ -0,0 +1,11 @@ +package openrtb_ext + +import "encoding/json" + +// ExtImpBidmatic defines the contract for bidrequest.imp[i].ext.prebid.bidder.bidmatic +type ExtImpBidmatic struct { + SourceId json.Number `json:"source"` + PlacementId int `json:"placementId,omitempty"` + SiteId int `json:"siteId,omitempty"` + BidFloor float64 `json:"bidFloor,omitempty"` +} diff --git a/openrtb_ext/imp_bigoad.go b/openrtb_ext/imp_bigoad.go new file mode 100644 index 00000000000..dad9c4fd878 --- /dev/null +++ b/openrtb_ext/imp_bigoad.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpBigoAd struct { + SspId string `json:"sspid"` +} diff --git a/openrtb_ext/imp_bizzclick.go b/openrtb_ext/imp_blasto.go similarity index 51% rename from openrtb_ext/imp_bizzclick.go rename to openrtb_ext/imp_blasto.go index 5090a822ec5..b49544895a9 100644 --- a/openrtb_ext/imp_bizzclick.go +++ b/openrtb_ext/imp_blasto.go @@ -1,6 +1,8 @@ package openrtb_ext -type ExtBizzclick struct { +type ExtBlasto struct { AccountID string `json:"accountId"` + SourceID string `json:"sourceId"` + Host string `json:"host"` PlacementID string `json:"placementId"` } diff --git a/openrtb_ext/imp_bwx.go b/openrtb_ext/imp_bwx.go new file mode 100644 index 00000000000..9181beb05b0 --- /dev/null +++ b/openrtb_ext/imp_bwx.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtBWX struct { + Env string `json:"env"` + Pid string `json:"pid"` +} diff --git a/openrtb_ext/imp_cointraffic.go b/openrtb_ext/imp_cointraffic.go new file mode 100644 index 00000000000..da5ea69b1b5 --- /dev/null +++ b/openrtb_ext/imp_cointraffic.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpCointraffic struct { + PlacementId string `json:"placementId"` +} diff --git a/openrtb_ext/imp_concert.go b/openrtb_ext/imp_concert.go new file mode 100644 index 00000000000..bd5ec245c86 --- /dev/null +++ b/openrtb_ext/imp_concert.go @@ -0,0 +1,9 @@ +package openrtb_ext + +type ImpExtConcert struct { + PartnerId string `json:"partnerId"` + PlacementId *int `json:"placementId,omitempty"` + Site *string `json:"site,omitempty"` + Slot *string `json:"slot,omitempty"` + Sizes *[][]int `json:"sizes,omitempty"` +} diff --git a/openrtb_ext/imp_connectad.go b/openrtb_ext/imp_connectad.go index c4c7ab696f2..4affaed1ea3 100644 --- a/openrtb_ext/imp_connectad.go +++ b/openrtb_ext/imp_connectad.go @@ -1,7 +1,9 @@ package openrtb_ext +import "github.com/prebid/prebid-server/v3/util/jsonutil" + type ExtImpConnectAd struct { - NetworkID int `json:"networkId"` - SiteID int `json:"siteId"` - Bidfloor float64 `json:"bidfloor,omitempty"` + NetworkID jsonutil.StringInt `json:"networkId"` + SiteID jsonutil.StringInt `json:"siteId"` + Bidfloor float64 `json:"bidfloor,omitempty"` } diff --git a/openrtb_ext/imp_consumable.go b/openrtb_ext/imp_consumable.go index fe916b09972..8158b41d2cc 100644 --- a/openrtb_ext/imp_consumable.go +++ b/openrtb_ext/imp_consumable.go @@ -6,5 +6,6 @@ type ExtImpConsumable struct { SiteId int `json:"siteId,omitempty"` UnitId int `json:"unitId,omitempty"` /* UnitName gets used as a classname and in the URL when building the ad markup */ - UnitName string `json:"unitName,omitempty"` + UnitName string `json:"unitName,omitempty"` + PlacementId string `json:"placementid,omitempty"` } diff --git a/openrtb_ext/imp_copper6ssp.go b/openrtb_ext/imp_copper6ssp.go new file mode 100644 index 00000000000..a9fd47a1eb6 --- /dev/null +++ b/openrtb_ext/imp_copper6ssp.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtCopper6ssp struct { + PlacementID string `json:"placementId"` + EndpointID string `json:"endpointId"` +} diff --git a/openrtb_ext/imp_displayio.go b/openrtb_ext/imp_displayio.go new file mode 100644 index 00000000000..bb8c2020276 --- /dev/null +++ b/openrtb_ext/imp_displayio.go @@ -0,0 +1,7 @@ +package openrtb_ext + +type ExtImpDisplayio struct { + PublisherId string `json:"publisherId"` + InventoryId string `json:"inventoryId"` + PlacementId string `json:"placementId"` +} diff --git a/openrtb_ext/imp_driftpixel.go b/openrtb_ext/imp_driftpixel.go new file mode 100644 index 00000000000..a1505d54ae4 --- /dev/null +++ b/openrtb_ext/imp_driftpixel.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtDriftPixel struct { + Env string `json:"env"` + Pid string `json:"pid"` +} diff --git a/openrtb_ext/imp_escalax.go b/openrtb_ext/imp_escalax.go new file mode 100644 index 00000000000..15292b59552 --- /dev/null +++ b/openrtb_ext/imp_escalax.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtEscalax struct { + AccountID string `json:"accountId"` + SourceID string `json:"sourceId"` +} diff --git a/openrtb_ext/imp_freewheelssp.go b/openrtb_ext/imp_freewheelssp.go index 110f018f512..5ed1f626093 100644 --- a/openrtb_ext/imp_freewheelssp.go +++ b/openrtb_ext/imp_freewheelssp.go @@ -1,7 +1,7 @@ package openrtb_ext import ( - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type ImpExtFreewheelSSP struct { diff --git a/openrtb_ext/imp_gumgum.go b/openrtb_ext/imp_gumgum.go index 96a1308d663..f54234fa394 100644 --- a/openrtb_ext/imp_gumgum.go +++ b/openrtb_ext/imp_gumgum.go @@ -3,10 +3,11 @@ package openrtb_ext // ExtImpGumGum defines the contract for bidrequest.imp[i].ext.prebid.bidder.gumgum // Either Zone or PubId must be present, others are optional parameters type ExtImpGumGum struct { - Zone string `json:"zone,omitempty"` - PubID float64 `json:"pubId,omitempty"` - IrisID string `json:"irisid,omitempty"` - Slot float64 `json:"slot,omitempty"` + Zone string `json:"zone,omitempty"` + PubID float64 `json:"pubId,omitempty"` + IrisID string `json:"irisid,omitempty"` + Slot float64 `json:"slot,omitempty"` + Product string `json:"product,omitempty"` } // ExtImpGumGumVideo defines the contract for bidresponse.seatbid.bid[i].ext.prebid.bidder.gumgum.video diff --git a/openrtb_ext/imp_loyal.go b/openrtb_ext/imp_loyal.go new file mode 100644 index 00000000000..61e3e49727d --- /dev/null +++ b/openrtb_ext/imp_loyal.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtLoyal struct { + PlacementID string `json:"placementId"` + EndpointID string `json:"endpointId"` +} diff --git a/openrtb_ext/imp_mediago.go b/openrtb_ext/imp_mediago.go new file mode 100644 index 00000000000..89a5ff0dade --- /dev/null +++ b/openrtb_ext/imp_mediago.go @@ -0,0 +1,13 @@ +package openrtb_ext + +// ExtImpMediaGo defines the contract for bidrequest.imp[i].ext.prebid.bidder.mediago +type ExtImpMediaGo struct { + Token string `json:"token"` + Region string `json:"region"` + PlacementId string `json:"placementId"` +} + +type ExtMediaGo struct { + Token string `json:"token"` + Region string `json:"region"` +} diff --git a/openrtb_ext/imp_melozen.go b/openrtb_ext/imp_melozen.go new file mode 100644 index 00000000000..598df6a28e9 --- /dev/null +++ b/openrtb_ext/imp_melozen.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ImpExtMeloZen struct { + PubId string `json:"pubId"` +} diff --git a/openrtb_ext/imp_metax.go b/openrtb_ext/imp_metax.go new file mode 100644 index 00000000000..ab54505fbf3 --- /dev/null +++ b/openrtb_ext/imp_metax.go @@ -0,0 +1,7 @@ +package openrtb_ext + +// ExtImpMetaX defines the contract for bidrequest.imp[i].ext.prebid.bidder.metax +type ExtImpMetaX struct { + PublisherID int `json:"publisherId"` + Adunit int `json:"adunit"` +} diff --git a/openrtb_ext/imp_minutemedia.go b/openrtb_ext/imp_minutemedia.go new file mode 100644 index 00000000000..197f3b0a85b --- /dev/null +++ b/openrtb_ext/imp_minutemedia.go @@ -0,0 +1,6 @@ +package openrtb_ext + +// ImpExtMinuteMedia defines the contract for bidrequest.imp[i].ext.prebid.bidder.minutemedia +type ImpExtMinuteMedia struct { + Org string `json:"org"` +} diff --git a/openrtb_ext/imp_missena.go b/openrtb_ext/imp_missena.go new file mode 100644 index 00000000000..3e341957123 --- /dev/null +++ b/openrtb_ext/imp_missena.go @@ -0,0 +1,7 @@ +package openrtb_ext + +type ExtImpMissena struct { + ApiKey string `json:"apiKey"` + Placement string `json:"placement"` + TestMode string `json:"test"` +} diff --git a/openrtb_ext/imp_oms.go b/openrtb_ext/imp_oms.go new file mode 100644 index 00000000000..4e12da86264 --- /dev/null +++ b/openrtb_ext/imp_oms.go @@ -0,0 +1,6 @@ +package openrtb_ext + +// ExtImpOms defines the contract for bidrequest.imp[i].ext.prebid.bidder.oms +type ExtImpOms struct { + PublisherID string `json:"pid"` +} diff --git a/openrtb_ext/imp_openweb.go b/openrtb_ext/imp_openweb.go index fc3cbdacdd0..5df8e7d18e5 100644 --- a/openrtb_ext/imp_openweb.go +++ b/openrtb_ext/imp_openweb.go @@ -2,8 +2,7 @@ package openrtb_ext // ExtImpOpenWeb defines the contract for bidrequest.imp[i].ext.prebid.bidder.openweb type ExtImpOpenWeb struct { - SourceID int `json:"aid"` - PlacementID int `json:"placementId,omitempty"` - SiteID int `json:"siteId,omitempty"` - BidFloor float64 `json:"bidFloor,omitempty"` + Aid int `json:"aid,omitempty"` + Org string `json:"org,omitempty"` + PlacementID string `json:"placementId"` } diff --git a/openrtb_ext/imp_openx.go b/openrtb_ext/imp_openx.go index 38bce75f17c..880e4a6d457 100644 --- a/openrtb_ext/imp_openx.go +++ b/openrtb_ext/imp_openx.go @@ -1,10 +1,12 @@ package openrtb_ext +import "encoding/json" + // ExtImpOpenx defines the contract for bidrequest.imp[i].ext.prebid.bidder.openx type ExtImpOpenx struct { - Unit string `json:"unit"` + Unit json.Number `json:"unit"` Platform string `json:"platform"` DelDomain string `json:"delDomain"` - CustomFloor float64 `json:"customFloor"` + CustomFloor json.Number `json:"customFloor"` CustomParams map[string]interface{} `json:"customParams"` } diff --git a/openrtb_ext/imp_oraki.go b/openrtb_ext/imp_oraki.go new file mode 100644 index 00000000000..a9dea04434f --- /dev/null +++ b/openrtb_ext/imp_oraki.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtOraki struct { + PlacementID string `json:"placementId"` + EndpointID string `json:"endpointId"` +} diff --git a/openrtb_ext/imp_playdigo.go b/openrtb_ext/imp_playdigo.go new file mode 100644 index 00000000000..2d2d54ada20 --- /dev/null +++ b/openrtb_ext/imp_playdigo.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtPlaydigo struct { + PlacementID string `json:"placementId"` + EndpointID string `json:"endpointId"` +} diff --git a/openrtb_ext/imp_pubrise.go b/openrtb_ext/imp_pubrise.go new file mode 100644 index 00000000000..c2b30391748 --- /dev/null +++ b/openrtb_ext/imp_pubrise.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtPubrise struct { + PlacementID string `json:"placementId"` + EndpointID string `json:"endpointId"` +} diff --git a/openrtb_ext/imp_pulsepoint.go b/openrtb_ext/imp_pulsepoint.go index a901592c45b..a372e3765c5 100644 --- a/openrtb_ext/imp_pulsepoint.go +++ b/openrtb_ext/imp_pulsepoint.go @@ -1,9 +1,13 @@ package openrtb_ext +import ( + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + // ExtImpPulsePoint defines the json spec for bidrequest.imp[i].ext.prebid.bidder.pulsepoint // PubId/TagId are mandatory params type ExtImpPulsePoint struct { - PubID int `json:"cp"` - TagID int `json:"ct"` + PubID jsonutil.StringInt `json:"cp"` + TagID jsonutil.StringInt `json:"ct"` } diff --git a/openrtb_ext/imp_qt.go b/openrtb_ext/imp_qt.go new file mode 100644 index 00000000000..78e4d11e746 --- /dev/null +++ b/openrtb_ext/imp_qt.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtQT struct { + PlacementID string `json:"placementId"` + EndpointID string `json:"endpointId"` +} diff --git a/openrtb_ext/imp_readpeak.go b/openrtb_ext/imp_readpeak.go new file mode 100644 index 00000000000..0c65af4d361 --- /dev/null +++ b/openrtb_ext/imp_readpeak.go @@ -0,0 +1,8 @@ +package openrtb_ext + +type ImpExtReadpeak struct { + PublisherId string `json:"publisherId"` + SiteId string `json:"siteId"` + Bidfloor float64 `json:"bidfloor"` + TagId string `json:"tagId"` +} diff --git a/openrtb_ext/imp_relevantdigital.go b/openrtb_ext/imp_relevantdigital.go new file mode 100644 index 00000000000..ec250557c2b --- /dev/null +++ b/openrtb_ext/imp_relevantdigital.go @@ -0,0 +1,8 @@ +package openrtb_ext + +type ExtRelevantDigital struct { + AccountId string `json:"accountId"` + PlacementId string `json:"placementId"` + Host string `json:"pbsHost"` + PbsBufferMs int `json:"pbsBufferMs"` +} diff --git a/openrtb_ext/imp_rise.go b/openrtb_ext/imp_rise.go index 7311f05d52d..de37e4f800d 100644 --- a/openrtb_ext/imp_rise.go +++ b/openrtb_ext/imp_rise.go @@ -4,4 +4,5 @@ package openrtb_ext type ImpExtRise struct { PublisherID string `json:"publisher_id"` Org string `json:"org"` + PlacementID string `json:"placementId"` } diff --git a/openrtb_ext/imp_roulax.go b/openrtb_ext/imp_roulax.go new file mode 100644 index 00000000000..8ce720f42ba --- /dev/null +++ b/openrtb_ext/imp_roulax.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtImpRoulax struct { + Pid string `json:"Pid,omitempty"` + PublisherPath string `json:"publisherPath,omitempty"` +} diff --git a/openrtb_ext/imp_seedingAlliance.go b/openrtb_ext/imp_seedingAlliance.go index 759683ad6b3..d383ad39d6e 100644 --- a/openrtb_ext/imp_seedingAlliance.go +++ b/openrtb_ext/imp_seedingAlliance.go @@ -1,5 +1,7 @@ package openrtb_ext type ImpExtSeedingAlliance struct { - AdUnitID string `json:"adUnitID"` + AdUnitID string `json:"adUnitId"` + SeatID string `json:"seatId"` + AccountID string `json:"accountId"` } diff --git a/openrtb_ext/imp_smrtconnect.go b/openrtb_ext/imp_smrtconnect.go new file mode 100644 index 00000000000..c3e6b0cd7e9 --- /dev/null +++ b/openrtb_ext/imp_smrtconnect.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtSmrtconnect struct { + SupplyId string `json:"supply_id"` +} diff --git a/openrtb_ext/imp_sovrnXsp.go b/openrtb_ext/imp_sovrnXsp.go new file mode 100644 index 00000000000..90c78d707e4 --- /dev/null +++ b/openrtb_ext/imp_sovrnXsp.go @@ -0,0 +1,8 @@ +package openrtb_ext + +type ExtImpSovrnXsp struct { + PubID string `json:"pub_id,omitempty"` + MedID string `json:"med_id,omitempty"` + ZoneID string `json:"zone_id,omitempty"` + ForceBid bool `json:"force_bid,omitempty"` +} diff --git a/openrtb_ext/imp_suntContent.go b/openrtb_ext/imp_suntContent.go deleted file mode 100644 index 5040df7e3b6..00000000000 --- a/openrtb_ext/imp_suntContent.go +++ /dev/null @@ -1,5 +0,0 @@ -package openrtb_ext - -type ImpExtSuntContent struct { - AdUnitID string `json:"adUnitID"` -} diff --git a/openrtb_ext/imp_theadx.go b/openrtb_ext/imp_theadx.go new file mode 100644 index 00000000000..9910aeccd53 --- /dev/null +++ b/openrtb_ext/imp_theadx.go @@ -0,0 +1,12 @@ +package openrtb_ext + +import ( + "encoding/json" +) + +type ExtImpTheadx struct { + TagID json.Number `json:"tagid"` + InventorySourceID int `json:"wid,omitempty"` + MemberID int `json:"pid,omitempty"` + PlacementName string `json:"pname,omitempty"` +} diff --git a/openrtb_ext/imp_thetradedesk.go b/openrtb_ext/imp_thetradedesk.go new file mode 100644 index 00000000000..89cca83f65e --- /dev/null +++ b/openrtb_ext/imp_thetradedesk.go @@ -0,0 +1,8 @@ +package openrtb_ext + +// ExtImpTheTradeDesk defines the contract for bidrequest.imp[i].ext +// PublisherId is mandatory parameters, others are optional parameters + +type ExtImpTheTradeDesk struct { + PublisherId string `json:"publisherId"` +} diff --git a/openrtb_ext/imp_trustedstack.go b/openrtb_ext/imp_trustedstack.go new file mode 100644 index 00000000000..22914929689 --- /dev/null +++ b/openrtb_ext/imp_trustedstack.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtImpTrustedstack struct { + Cid string `json:"cid"` + Crid string `json:"crid"` +} diff --git a/openrtb_ext/imp_vidazoo.go b/openrtb_ext/imp_vidazoo.go new file mode 100644 index 00000000000..c2589d3f1ae --- /dev/null +++ b/openrtb_ext/imp_vidazoo.go @@ -0,0 +1,6 @@ +package openrtb_ext + +// ImpExtVidazoo defines the contract for bidrequest.imp[i].ext.prebid.bidder.vidazoo +type ImpExtVidazoo struct { + ConnectionId string `json:"cId"` +} diff --git a/openrtb_ext/imp_liftoff.go b/openrtb_ext/imp_vungle.go similarity index 85% rename from openrtb_ext/imp_liftoff.go rename to openrtb_ext/imp_vungle.go index d8a93d8906a..00a87832f10 100644 --- a/openrtb_ext/imp_liftoff.go +++ b/openrtb_ext/imp_vungle.go @@ -1,6 +1,6 @@ package openrtb_ext -type ImpExtLiftoff struct { +type ImpExtVungle struct { BidToken string `json:"bid_token"` PubAppStoreID string `json:"app_store_id"` PlacementRefID string `json:"placement_reference_id"` diff --git a/openrtb_ext/imp_yandex.go b/openrtb_ext/imp_yandex.go new file mode 100644 index 00000000000..8439d95f845 --- /dev/null +++ b/openrtb_ext/imp_yandex.go @@ -0,0 +1,16 @@ +package openrtb_ext + +type ExtImpYandex struct { + /* + Possible formats + - `R-I-123456-2` + - `R-123456-1` + - `123456-789` + */ + PlacementID string `json:"placement_id"` + + // Deprecated: in favor of `PlacementID` + PageID int64 `json:"page_id"` + // Deprecated: in favor of `PlacementID` + ImpID int64 `json:"imp_id"` +} diff --git a/openrtb_ext/imp_zmaticoo.go b/openrtb_ext/imp_zmaticoo.go new file mode 100644 index 00000000000..6746493798b --- /dev/null +++ b/openrtb_ext/imp_zmaticoo.go @@ -0,0 +1,7 @@ +package openrtb_ext + +// ExtImpZmaticoo defines the contract for bidrequest.imp[i].ext.prebid.bidder.zmaticoo +type ExtImpZmaticoo struct { + PubId string `json:"pubId"` + ZoneId string `json:"zoneId"` +} diff --git a/openrtb_ext/multibid_test.go b/openrtb_ext/multibid_test.go index a27631d6891..5a552653119 100644 --- a/openrtb_ext/multibid_test.go +++ b/openrtb_ext/multibid_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" ) diff --git a/openrtb_ext/regs.go b/openrtb_ext/regs.go index 56a5179d051..2b395d49954 100644 --- a/openrtb_ext/regs.go +++ b/openrtb_ext/regs.go @@ -1,7 +1,11 @@ package openrtb_ext +import "slices" + // ExtRegs defines the contract for bidrequest.regs.ext type ExtRegs struct { + // DSA is an object containing DSA transparency information, see https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/dsa_transparency.md + DSA *ExtRegsDSA `json:"dsa,omitempty"` // GDPR should be "1" if the caller believes the user is subject to GDPR laws, "0" if not, and undefined // if it's unknown. For more info on this parameter, see: https://iabtechlab.com/wp-content/uploads/2018/02/OpenRTB_Advisory_GDPR_2018-02.pdf @@ -10,3 +14,48 @@ type ExtRegs struct { // USPrivacy should be a four character string, see: https://iabtechlab.com/wp-content/uploads/2019/11/OpenRTB-Extension-U.S.-Privacy-IAB-Tech-Lab.pdf USPrivacy string `json:"us_privacy,omitempty"` } + +// ExtRegsDSA defines the contract for bidrequest.regs.ext.dsa +type ExtRegsDSA struct { + Required *int8 `json:"dsarequired,omitempty"` + PubRender *int8 `json:"pubrender,omitempty"` + DataToPub *int8 `json:"datatopub,omitempty"` + Transparency []ExtBidDSATransparency `json:"transparency,omitempty"` +} + +// Clone creates a deep copy of ExtRegsDSA +func (erd *ExtRegsDSA) Clone() *ExtRegsDSA { + if erd == nil { + return nil + } + clone := *erd + + if erd.Required != nil { + clonedRequired := *erd.Required + clone.Required = &clonedRequired + } + if erd.PubRender != nil { + clonedPubRender := *erd.PubRender + clone.PubRender = &clonedPubRender + } + if erd.DataToPub != nil { + clonedDataToPub := *erd.DataToPub + clone.DataToPub = &clonedDataToPub + } + if erd.Transparency != nil { + clonedTransparency := make([]ExtBidDSATransparency, len(erd.Transparency)) + for i, transparency := range erd.Transparency { + newTransparency := transparency + newTransparency.Params = slices.Clone(transparency.Params) + clonedTransparency[i] = newTransparency + } + clone.Transparency = clonedTransparency + } + return &clone +} + +// ExtBidDSATransparency defines the contract for bidrequest.regs.ext.dsa.transparency +type ExtBidDSATransparency struct { + Domain string `json:"domain,omitempty"` + Params []int `json:"dsaparams,omitempty"` +} diff --git a/openrtb_ext/regs_test.go b/openrtb_ext/regs_test.go new file mode 100644 index 00000000000..064282c1f3f --- /dev/null +++ b/openrtb_ext/regs_test.go @@ -0,0 +1,97 @@ +package openrtb_ext + +import ( + "testing" + + "github.com/prebid/prebid-server/v3/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +func TestExtRegsDSAClone(t *testing.T) { + tests := []struct { + name string + extRegsDSA *ExtRegsDSA + }{ + { + name: "nil", + extRegsDSA: nil, + }, + { + name: "required_not_nil", + extRegsDSA: &ExtRegsDSA{ + Required: ptrutil.ToPtr[int8](1), + }, + }, + { + name: "pubrender_not_nil", + extRegsDSA: &ExtRegsDSA{ + PubRender: ptrutil.ToPtr[int8](1), + }, + }, + { + name: "datatopub_not_nil", + extRegsDSA: &ExtRegsDSA{ + DataToPub: ptrutil.ToPtr[int8](1), + }, + }, + { + name: "transparency_empty", + extRegsDSA: &ExtRegsDSA{ + Transparency: []ExtBidDSATransparency{}, + }, + }, + { + name: "transparency_with_nil_params", + extRegsDSA: &ExtRegsDSA{ + Transparency: []ExtBidDSATransparency{ + { + Domain: "domain1", + Params: nil, + }, + }, + }, + }, + { + name: "transparency_with_params", + extRegsDSA: &ExtRegsDSA{ + Required: ptrutil.ToPtr[int8](1), + PubRender: ptrutil.ToPtr[int8](1), + DataToPub: ptrutil.ToPtr[int8](1), + Transparency: []ExtBidDSATransparency{ + { + Domain: "domain1", + Params: []int{1, 2, 3}, + }, + { + Domain: "domain2", + Params: []int{4, 5, 6}, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + clone := tt.extRegsDSA.Clone() + if tt.extRegsDSA == nil { + assert.Nil(t, clone) + } else { + assert.Equal(t, tt.extRegsDSA, clone) + + if tt.extRegsDSA.Required != nil { + assert.NotSame(t, tt.extRegsDSA.Required, clone.Required) + } + if tt.extRegsDSA.PubRender != nil { + assert.NotSame(t, tt.extRegsDSA.PubRender, clone.PubRender) + } + if tt.extRegsDSA.DataToPub != nil { + assert.NotSame(t, tt.extRegsDSA.DataToPub, clone.DataToPub) + } + if tt.extRegsDSA.Transparency != nil { + assert.NotSame(t, tt.extRegsDSA.Transparency, clone.Transparency) + } + } + }) + } +} diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 3a41fdf0dbb..46cdb1a674a 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -3,12 +3,12 @@ package openrtb_ext import ( "encoding/json" "fmt" + "maps" + "slices" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/maputil" - "github.com/prebid/prebid-server/util/ptrutil" - "github.com/prebid/prebid-server/util/sliceutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" ) // FirstPartyDataExtKey defines a field name within request.ext and request.imp.ext reserved for first party data. @@ -45,6 +45,7 @@ type ExtRequestPrebid struct { AdServerTargeting []AdServerTarget `json:"adservertargeting,omitempty"` Aliases map[string]string `json:"aliases,omitempty"` AliasGVLIDs map[string]uint16 `json:"aliasgvlids,omitempty"` + Analytics map[string]json.RawMessage `json:"analytics,omitempty"` BidAdjustmentFactors map[string]float64 `json:"bidadjustmentfactors,omitempty"` BidAdjustments *ExtRequestPrebidBidAdjustments `json:"bidadjustments,omitempty"` BidderConfigs []BidderConfig `json:"bidderconfig,omitempty"` @@ -164,7 +165,7 @@ type ExtRequestPrebidCacheVAST struct { // ExtRequestPrebidBidAdjustments defines the contract for bidrequest.ext.prebid.bidadjustments type ExtRequestPrebidBidAdjustments struct { - MediaType MediaType `json:"mediatype,omitempty"` + MediaType MediaType `mapstructure:"mediatype" json:"mediatype,omitempty"` } // AdjustmentsByDealID maps a dealID to a slice of bid adjustments @@ -173,37 +174,38 @@ type AdjustmentsByDealID map[string][]Adjustment // MediaType defines contract for bidrequest.ext.prebid.bidadjustments.mediatype // BidderName will map to a DealID that will map to a slice of bid adjustments type MediaType struct { - Banner map[BidderName]AdjustmentsByDealID `json:"banner,omitempty"` - VideoInstream map[BidderName]AdjustmentsByDealID `json:"video-instream,omitempty"` - VideoOutstream map[BidderName]AdjustmentsByDealID `json:"video-outstream,omitempty"` - Audio map[BidderName]AdjustmentsByDealID `json:"audio,omitempty"` - Native map[BidderName]AdjustmentsByDealID `json:"native,omitempty"` - WildCard map[BidderName]AdjustmentsByDealID `json:"*,omitempty"` + Banner map[BidderName]AdjustmentsByDealID `mapstructure:"banner" json:"banner,omitempty"` + VideoInstream map[BidderName]AdjustmentsByDealID `mapstructure:"video-instream" json:"video-instream,omitempty"` + VideoOutstream map[BidderName]AdjustmentsByDealID `mapstructure:"video-outstream" json:"video-outstream,omitempty"` + Audio map[BidderName]AdjustmentsByDealID `mapstructure:"audio" json:"audio,omitempty"` + Native map[BidderName]AdjustmentsByDealID `mapstructure:"native" json:"native,omitempty"` + WildCard map[BidderName]AdjustmentsByDealID `mapstructure:"*" json:"*,omitempty"` } // Adjustment defines the object that will be present in the slice of bid adjustments found from MediaType map type Adjustment struct { - Type string `json:"adjtype,omitempty"` - Value float64 `json:"value,omitempty"` - Currency string `json:"currency,omitempty"` + Type string `mapstructure:"adjtype" json:"adjtype,omitempty"` + Value float64 `mapstructure:"value" json:"value,omitempty"` + Currency string `mapstructure:"currency" json:"currency,omitempty"` } // ExtRequestTargeting defines the contract for bidrequest.ext.prebid.targeting type ExtRequestTargeting struct { - PriceGranularity *PriceGranularity `json:"pricegranularity,omitempty"` - MediaTypePriceGranularity MediaTypePriceGranularity `json:"mediatypepricegranularity,omitempty"` - IncludeWinners *bool `json:"includewinners,omitempty"` - IncludeBidderKeys *bool `json:"includebidderkeys,omitempty"` - IncludeBrandCategory *ExtIncludeBrandCategory `json:"includebrandcategory,omitempty"` - IncludeFormat bool `json:"includeformat,omitempty"` - DurationRangeSec []int `json:"durationrangesec,omitempty"` - PreferDeals bool `json:"preferdeals,omitempty"` - AppendBidderNames bool `json:"appendbiddernames,omitempty"` + PriceGranularity *PriceGranularity `json:"pricegranularity,omitempty"` + MediaTypePriceGranularity *MediaTypePriceGranularity `json:"mediatypepricegranularity,omitempty"` + IncludeWinners *bool `json:"includewinners,omitempty"` + IncludeBidderKeys *bool `json:"includebidderkeys,omitempty"` + IncludeBrandCategory *ExtIncludeBrandCategory `json:"includebrandcategory,omitempty"` + IncludeFormat bool `json:"includeformat,omitempty"` + DurationRangeSec []int `json:"durationrangesec,omitempty"` + PreferDeals bool `json:"preferdeals,omitempty"` + AppendBidderNames bool `json:"appendbiddernames,omitempty"` + AlwaysIncludeDeals bool `json:"alwaysincludedeals,omitempty"` } type ExtIncludeBrandCategory struct { - PrimaryAdServer int `json:"primaryadserver"` - Publisher string `json:"publisher"` + PrimaryAdServer int `json:"primaryadserver,omitempty"` + Publisher string `json:"publisher,omitempty"` WithCategory bool `json:"withcategory"` TranslateCategories *bool `json:"translatecategories,omitempty"` } @@ -379,14 +381,14 @@ func (erp *ExtRequestPrebid) Clone() *ExtRequestPrebid { } clone := *erp - clone.Aliases = maputil.Clone(erp.Aliases) - clone.AliasGVLIDs = maputil.Clone(erp.AliasGVLIDs) - clone.BidAdjustmentFactors = maputil.Clone(erp.BidAdjustmentFactors) + clone.Aliases = maps.Clone(erp.Aliases) + clone.AliasGVLIDs = maps.Clone(erp.AliasGVLIDs) + clone.BidAdjustmentFactors = maps.Clone(erp.BidAdjustmentFactors) if erp.BidderConfigs != nil { clone.BidderConfigs = make([]BidderConfig, len(erp.BidderConfigs)) for i, bc := range erp.BidderConfigs { - clonedBidderConfig := BidderConfig{Bidders: sliceutil.Clone(bc.Bidders)} + clonedBidderConfig := BidderConfig{Bidders: slices.Clone(bc.Bidders)} if bc.Config != nil { config := &Config{ORTB2: ptrutil.Clone(bc.Config.ORTB2)} clonedBidderConfig.Config = config @@ -415,7 +417,7 @@ func (erp *ExtRequestPrebid) Clone() *ExtRequestPrebid { if erp.CurrencyConversions != nil { newConvRates := make(map[string]map[string]float64, len(erp.CurrencyConversions.ConversionRates)) for key, val := range erp.CurrencyConversions.ConversionRates { - newConvRates[key] = maputil.Clone(val) + newConvRates[key] = maps.Clone(val) } clone.CurrencyConversions = &ExtRequestCurrency{ConversionRates: newConvRates} if erp.CurrencyConversions.UsePBSRates != nil { @@ -424,13 +426,13 @@ func (erp *ExtRequestPrebid) Clone() *ExtRequestPrebid { } if erp.Data != nil { - clone.Data = &ExtRequestPrebidData{Bidders: sliceutil.Clone(erp.Data.Bidders)} + clone.Data = &ExtRequestPrebidData{Bidders: slices.Clone(erp.Data.Bidders)} if erp.Data.EidPermissions != nil { newEidPermissions := make([]ExtRequestPrebidDataEidPermission, len(erp.Data.EidPermissions)) for i, eidp := range erp.Data.EidPermissions { newEidPermissions[i] = ExtRequestPrebidDataEidPermission{ Source: eidp.Source, - Bidders: sliceutil.Clone(eidp.Bidders), + Bidders: slices.Clone(eidp.Bidders), } } clone.Data.EidPermissions = newEidPermissions @@ -449,7 +451,7 @@ func (erp *ExtRequestPrebid) Clone() *ExtRequestPrebid { for i, mulBid := range erp.MultiBid { newMulBid := &ExtMultiBid{ Bidder: mulBid.Bidder, - Bidders: sliceutil.Clone(mulBid.Bidders), + Bidders: slices.Clone(mulBid.Bidders), TargetBidderCodePrefix: mulBid.TargetBidderCodePrefix, } if mulBid.MaxBids != nil { @@ -463,7 +465,7 @@ func (erp *ExtRequestPrebid) Clone() *ExtRequestPrebid { clone.SChains = make([]*ExtRequestPrebidSChain, len(erp.SChains)) for i, schain := range erp.SChains { newChain := *schain - newNodes := sliceutil.Clone(schain.SChain.Nodes) + newNodes := slices.Clone(schain.SChain.Nodes) for j, node := range newNodes { if node.HP != nil { newNodes[j].HP = ptrutil.ToPtr(*newNodes[j].HP) @@ -481,13 +483,13 @@ func (erp *ExtRequestPrebid) Clone() *ExtRequestPrebid { if erp.Targeting != nil { newTargeting := &ExtRequestTargeting{ IncludeFormat: erp.Targeting.IncludeFormat, - DurationRangeSec: sliceutil.Clone(erp.Targeting.DurationRangeSec), + DurationRangeSec: slices.Clone(erp.Targeting.DurationRangeSec), PreferDeals: erp.Targeting.PreferDeals, AppendBidderNames: erp.Targeting.AppendBidderNames, } if erp.Targeting.PriceGranularity != nil { newPriceGranularity := &PriceGranularity{ - Ranges: sliceutil.Clone(erp.Targeting.PriceGranularity.Ranges), + Ranges: slices.Clone(erp.Targeting.PriceGranularity.Ranges), } newPriceGranularity.Precision = ptrutil.Clone(erp.Targeting.PriceGranularity.Precision) newTargeting.PriceGranularity = newPriceGranularity @@ -502,7 +504,7 @@ func (erp *ExtRequestPrebid) Clone() *ExtRequestPrebid { clone.Targeting = newTargeting } - clone.NoSale = sliceutil.Clone(erp.NoSale) + clone.NoSale = slices.Clone(erp.NoSale) if erp.AlternateBidderCodes != nil { newAlternateBidderCodes := ExtAlternateBidderCodes{Enabled: erp.AlternateBidderCodes.Enabled} @@ -511,7 +513,7 @@ func (erp *ExtRequestPrebid) Clone() *ExtRequestPrebid { for key, val := range erp.AlternateBidderCodes.Bidders { newBidders[key] = ExtAdapterAlternateBidderCodes{ Enabled: val.Enabled, - AllowedBidderCodes: sliceutil.Clone(val.AllowedBidderCodes), + AllowedBidderCodes: slices.Clone(val.AllowedBidderCodes), } } newAlternateBidderCodes.Bidders = newBidders @@ -529,8 +531,8 @@ func (erp *ExtRequestPrebid) Clone() *ExtRequestPrebid { for i, pfmg := range erp.Floors.Data.ModelGroups { clonedData.ModelGroups[i] = pfmg clonedData.ModelGroups[i].ModelWeight = ptrutil.Clone(pfmg.ModelWeight) - clonedData.ModelGroups[i].Schema.Fields = sliceutil.Clone(pfmg.Schema.Fields) - clonedData.ModelGroups[i].Values = maputil.Clone(pfmg.Values) + clonedData.ModelGroups[i].Schema.Fields = slices.Clone(pfmg.Schema.Fields) + clonedData.ModelGroups[i].Values = maps.Clone(pfmg.Values) } } clonedFloors.Data = &clonedData @@ -552,12 +554,12 @@ func (erp *ExtRequestPrebid) Clone() *ExtRequestPrebid { clone.MultiBidMap = make(map[string]ExtMultiBid, len(erp.MultiBidMap)) for k, v := range erp.MultiBidMap { // Make v a deep copy of the ExtMultiBid struct - v.Bidders = sliceutil.Clone(v.Bidders) + v.Bidders = slices.Clone(v.Bidders) v.MaxBids = ptrutil.Clone(v.MaxBids) clone.MultiBidMap[k] = v } } - clone.AdServerTargeting = sliceutil.Clone(erp.AdServerTargeting) + clone.AdServerTargeting = slices.Clone(erp.AdServerTargeting) return &clone } diff --git a/openrtb_ext/request_test.go b/openrtb_ext/request_test.go index ad6a655b022..c5f4abe22d4 100644 --- a/openrtb_ext/request_test.go +++ b/openrtb_ext/request_test.go @@ -5,9 +5,9 @@ import ( "fmt" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" ) diff --git a/openrtb_ext/request_wrapper.go b/openrtb_ext/request_wrapper.go index 09321e5b0b3..cb60948768f 100644 --- a/openrtb_ext/request_wrapper.go +++ b/openrtb_ext/request_wrapper.go @@ -3,12 +3,12 @@ package openrtb_ext import ( "encoding/json" "errors" + "maps" + "slices" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/maputil" - "github.com/prebid/prebid-server/util/ptrutil" - "github.com/prebid/prebid-server/util/sliceutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" ) // RequestWrapper wraps the OpenRTB request to provide a storage location for unmarshalled ext fields, so they @@ -53,12 +53,15 @@ const ( consentedProvidersSettingsListKey = "consented_providers_settings" consentKey = "consent" ampKey = "amp" + dsaKey = "dsa" eidsKey = "eids" gdprKey = "gdpr" prebidKey = "prebid" dataKey = "data" schainKey = "schain" us_privacyKey = "us_privacy" + cdepKey = "cdep" + gpcKey = "gpc" ) // LenImp returns the number of impressions without causing the creation of ImpWrapper objects. @@ -92,6 +95,12 @@ func (rw *RequestWrapper) GetImp() []*ImpWrapper { func (rw *RequestWrapper) SetImp(imps []*ImpWrapper) { rw.impWrappers = imps + imparr := make([]openrtb2.Imp, len(imps)) + for i, iw := range imps { + imparr[i] = *iw.Imp + iw.Imp = &imparr[i] + } + rw.Imp = imparr rw.impWrappersAccessed = true } @@ -236,6 +245,7 @@ func (rw *RequestWrapper) rebuildImp() error { return err } rw.Imp[i] = *rw.impWrappers[i].Imp + rw.impWrappers[i].Imp = &rw.Imp[i] } return nil @@ -389,6 +399,8 @@ func (rw *RequestWrapper) rebuildSourceExt() error { return nil } +// Clone clones the request wrapper exts and the imp wrappers +// the cloned imp wrappers are pointing to the bid request imps func (rw *RequestWrapper) Clone() *RequestWrapper { if rw == nil { return nil @@ -411,6 +423,26 @@ func (rw *RequestWrapper) Clone() *RequestWrapper { return &clone } +func (rw *RequestWrapper) CloneAndClearImpWrappers() *RequestWrapper { + if rw == nil { + return nil + } + rw.impWrappersAccessed = false + + clone := *rw + clone.impWrappers = nil + clone.userExt = rw.userExt.Clone() + clone.deviceExt = rw.deviceExt.Clone() + clone.requestExt = rw.requestExt.Clone() + clone.appExt = rw.appExt.Clone() + clone.regExt = rw.regExt.Clone() + clone.siteExt = rw.siteExt.Clone() + clone.doohExt = rw.doohExt.Clone() + clone.sourceExt = rw.sourceExt.Clone() + + return &clone +} + // --------------------------------------------------------------- // UserExt provides an interface for request.user.ext // --------------------------------------------------------------- @@ -647,7 +679,6 @@ func (ue *UserExt) SetConsentedProvidersSettingsOut(cpSettings *ConsentedProvide ue.consentedProvidersSettingsOut = cpSettings ue.consentedProvidersSettingsOutDirty = true - return } func (ue *UserExt) GetPrebid() *ExtUserPrebid { @@ -681,7 +712,7 @@ func (ue *UserExt) Clone() *UserExt { return nil } clone := *ue - clone.ext = maputil.Clone(ue.ext) + clone.ext = maps.Clone(ue.ext) if ue.consent != nil { clonedConsent := *ue.consent @@ -690,14 +721,14 @@ func (ue *UserExt) Clone() *UserExt { if ue.prebid != nil { clone.prebid = &ExtUserPrebid{} - clone.prebid.BuyerUIDs = maputil.Clone(ue.prebid.BuyerUIDs) + clone.prebid.BuyerUIDs = maps.Clone(ue.prebid.BuyerUIDs) } if ue.eids != nil { clonedEids := make([]openrtb2.EID, len(*ue.eids)) for i, eid := range *ue.eids { newEid := eid - newEid.UIDs = sliceutil.Clone(eid.UIDs) + newEid.UIDs = slices.Clone(eid.UIDs) clonedEids[i] = newEid } clone.eids = &clonedEids @@ -707,7 +738,7 @@ func (ue *UserExt) Clone() *UserExt { clone.consentedProvidersSettingsIn = &ConsentedProvidersSettingsIn{ConsentedProvidersString: ue.consentedProvidersSettingsIn.ConsentedProvidersString} } if ue.consentedProvidersSettingsOut != nil { - clone.consentedProvidersSettingsOut = &ConsentedProvidersSettingsOut{ConsentedProvidersList: sliceutil.Clone(ue.consentedProvidersSettingsOut.ConsentedProvidersList)} + clone.consentedProvidersSettingsOut = &ConsentedProvidersSettingsOut{ConsentedProvidersList: slices.Clone(ue.consentedProvidersSettingsOut.ConsentedProvidersList)} } return &clone @@ -858,7 +889,7 @@ func (re *RequestExt) Clone() *RequestExt { } clone := *re - clone.ext = maputil.Clone(re.ext) + clone.ext = maps.Clone(re.ext) if re.prebid != nil { clone.prebid = re.prebid.Clone() @@ -882,6 +913,8 @@ type DeviceExt struct { extDirty bool prebid *ExtDevicePrebid prebidDirty bool + cdep string + cdepDirty bool } func (de *DeviceExt) unmarshal(extJson json.RawMessage) error { @@ -909,6 +942,13 @@ func (de *DeviceExt) unmarshal(extJson json.RawMessage) error { } } + cdepJson, hasCDep := de.ext[cdepKey] + if hasCDep && cdepJson != nil { + if err := jsonutil.Unmarshal(cdepJson, &de.cdep); err != nil { + return err + } + } + return nil } @@ -930,6 +970,19 @@ func (de *DeviceExt) marshal() (json.RawMessage, error) { de.prebidDirty = false } + if de.cdepDirty { + if len(de.cdep) > 0 { + rawjson, err := jsonutil.Marshal(de.cdep) + if err != nil { + return nil, err + } + de.ext[cdepKey] = rawjson + } else { + delete(de.ext, cdepKey) + } + de.cdepDirty = false + } + de.extDirty = false if len(de.ext) == 0 { return nil, nil @@ -938,7 +991,7 @@ func (de *DeviceExt) marshal() (json.RawMessage, error) { } func (de *DeviceExt) Dirty() bool { - return de.extDirty || de.prebidDirty + return de.extDirty || de.prebidDirty || de.cdepDirty } func (de *DeviceExt) GetExt() map[string]json.RawMessage { @@ -967,13 +1020,22 @@ func (de *DeviceExt) SetPrebid(prebid *ExtDevicePrebid) { de.prebidDirty = true } +func (de *DeviceExt) GetCDep() string { + return de.cdep +} + +func (de *DeviceExt) SetCDep(cdep string) { + de.cdep = cdep + de.cdepDirty = true +} + func (de *DeviceExt) Clone() *DeviceExt { if de == nil { return nil } clone := *de - clone.ext = maputil.Clone(de.ext) + clone.ext = maps.Clone(de.ext) if de.prebid != nil { clonedPrebid := *de.prebid @@ -1087,7 +1149,7 @@ func (ae *AppExt) Clone() *AppExt { } clone := *ae - clone.ext = maputil.Clone(ae.ext) + clone.ext = maps.Clone(ae.ext) clone.prebid = ptrutil.Clone(ae.prebid) @@ -1153,7 +1215,7 @@ func (de *DOOHExt) Clone() *DOOHExt { } clone := *de - clone.ext = maputil.Clone(de.ext) + clone.ext = maps.Clone(de.ext) return &clone } @@ -1165,8 +1227,12 @@ func (de *DOOHExt) Clone() *DOOHExt { type RegExt struct { ext map[string]json.RawMessage extDirty bool + dsa *ExtRegsDSA + dsaDirty bool gdpr *int8 gdprDirty bool + gpc *string + gpcDirty bool usPrivacy string usPrivacyDirty bool } @@ -1186,6 +1252,16 @@ func (re *RegExt) unmarshal(extJson json.RawMessage) error { return err } + dsaJson, hasDSA := re.ext[dsaKey] + if hasDSA { + re.dsa = &ExtRegsDSA{} + } + if dsaJson != nil { + if err := jsonutil.Unmarshal(dsaJson, re.dsa); err != nil { + return err + } + } + gdprJson, hasGDPR := re.ext[gdprKey] if hasGDPR && gdprJson != nil { if err := jsonutil.Unmarshal(gdprJson, &re.gdpr); err != nil { @@ -1200,10 +1276,30 @@ func (re *RegExt) unmarshal(extJson json.RawMessage) error { } } + gpcJson, hasGPC := re.ext[gpcKey] + if hasGPC && gpcJson != nil { + if err := jsonutil.Unmarshal(gpcJson, &re.gpc); err != nil { + return err + } + } + return nil } func (re *RegExt) marshal() (json.RawMessage, error) { + if re.dsaDirty { + if re.dsa != nil { + rawjson, err := jsonutil.Marshal(re.dsa) + if err != nil { + return nil, err + } + re.ext[dsaKey] = rawjson + } else { + delete(re.ext, dsaKey) + } + re.dsaDirty = false + } + if re.gdprDirty { if re.gdpr != nil { rawjson, err := jsonutil.Marshal(re.gdpr) @@ -1230,6 +1326,19 @@ func (re *RegExt) marshal() (json.RawMessage, error) { re.usPrivacyDirty = false } + if re.gpcDirty { + if re.gpc != nil { + rawjson, err := jsonutil.Marshal(re.gpc) + if err != nil { + return nil, err + } + re.ext[gpcKey] = rawjson + } else { + delete(re.ext, gpcKey) + } + re.gpcDirty = false + } + re.extDirty = false if len(re.ext) == 0 { return nil, nil @@ -1238,7 +1347,7 @@ func (re *RegExt) marshal() (json.RawMessage, error) { } func (re *RegExt) Dirty() bool { - return re.extDirty || re.gdprDirty || re.usPrivacyDirty + return re.extDirty || re.dsaDirty || re.gdprDirty || re.usPrivacyDirty || re.gpcDirty } func (re *RegExt) GetExt() map[string]json.RawMessage { @@ -1254,9 +1363,25 @@ func (re *RegExt) SetExt(ext map[string]json.RawMessage) { re.extDirty = true } +func (re *RegExt) GetDSA() *ExtRegsDSA { + if re.dsa == nil { + return nil + } + dsa := *re.dsa + return &dsa +} + +func (re *RegExt) SetDSA(dsa *ExtRegsDSA) { + re.dsa = dsa + re.dsaDirty = true +} + func (re *RegExt) GetGDPR() *int8 { - gdpr := re.gdpr - return gdpr + if re.gdpr == nil { + return nil + } + gdpr := *re.gdpr + return &gdpr } func (re *RegExt) SetGDPR(gdpr *int8) { @@ -1264,6 +1389,19 @@ func (re *RegExt) SetGDPR(gdpr *int8) { re.gdprDirty = true } +func (re *RegExt) GetGPC() *string { + if re.gpc == nil { + return nil + } + gpc := *re.gpc + return &gpc +} + +func (re *RegExt) SetGPC(gpc *string) { + re.gpc = gpc + re.gpcDirty = true +} + func (re *RegExt) GetUSPrivacy() string { uSPrivacy := re.usPrivacy return uSPrivacy @@ -1280,7 +1418,7 @@ func (re *RegExt) Clone() *RegExt { } clone := *re - clone.ext = maputil.Clone(re.ext) + clone.ext = maps.Clone(re.ext) clone.gdpr = ptrutil.Clone(re.gdpr) @@ -1376,7 +1514,7 @@ func (se *SiteExt) Clone() *SiteExt { } clone := *se - clone.ext = maputil.Clone(se.ext) + clone.ext = maps.Clone(se.ext) clone.amp = ptrutil.Clone(se.amp) return &clone @@ -1479,7 +1617,7 @@ func (se *SourceExt) Clone() *SourceExt { } clone := *se - clone.ext = maputil.Clone(se.ext) + clone.ext = maps.Clone(se.ext) clone.schain = cloneSupplyChain(se.schain) @@ -1718,7 +1856,7 @@ func (e *ImpExt) Clone() *ImpExt { } clone := *e - clone.ext = maputil.Clone(e.ext) + clone.ext = maps.Clone(e.ext) if e.prebid != nil { clonedPrebid := *e.prebid @@ -1732,7 +1870,7 @@ func (e *ImpExt) Clone() *ImpExt { } } clonedPrebid.IsRewardedInventory = ptrutil.Clone(e.prebid.IsRewardedInventory) - clonedPrebid.Bidder = maputil.Clone(e.prebid.Bidder) + clonedPrebid.Bidder = maps.Clone(e.prebid.Bidder) clonedPrebid.Options = ptrutil.Clone(e.prebid.Options) clonedPrebid.Floors = ptrutil.Clone(e.prebid.Floors) clone.prebid = &clonedPrebid diff --git a/openrtb_ext/request_wrapper_test.go b/openrtb_ext/request_wrapper_test.go index 0127c545274..73851238d00 100644 --- a/openrtb_ext/request_wrapper_test.go +++ b/openrtb_ext/request_wrapper_test.go @@ -4,9 +4,9 @@ import ( "encoding/json" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -189,8 +189,10 @@ func TestUserExt(t *testing.T) { func TestRebuildImp(t *testing.T) { var ( - prebid = &ExtImpPrebid{IsRewardedInventory: openrtb2.Int8Ptr(1)} - prebidJson = json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`) + prebid = &ExtImpPrebid{IsRewardedInventory: openrtb2.Int8Ptr(1)} + prebidJson = json.RawMessage(`{"prebid":{"is_rewarded_inventory":1}}`) + prebidWithAdunitCode = &ExtImpPrebid{AdUnitCode: "adunitcode"} + prebidWithAdunitCodeJson = json.RawMessage(`{"prebid":{"adunitcode":"adunitcode"}}`) ) testCases := []struct { @@ -198,6 +200,7 @@ func TestRebuildImp(t *testing.T) { request openrtb2.BidRequest requestImpWrapper []*ImpWrapper expectedRequest openrtb2.BidRequest + expectedAccessed bool expectedError string }{ { @@ -217,11 +220,20 @@ func TestRebuildImp(t *testing.T) { request: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}}}, requestImpWrapper: []*ImpWrapper{{Imp: &openrtb2.Imp{ID: "2"}, impExt: &ImpExt{prebid: prebid, prebidDirty: true}}}, expectedRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "2", Ext: prebidJson}}}, + expectedAccessed: true, + }, + { + description: "One - Accessed - Dirty - AdUnitCode", + request: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}}}, + requestImpWrapper: []*ImpWrapper{{Imp: &openrtb2.Imp{ID: "1"}, impExt: &ImpExt{prebid: prebidWithAdunitCode, prebidDirty: true}}}, + expectedRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1", Ext: prebidWithAdunitCodeJson}}}, + expectedAccessed: true, }, { description: "One - Accessed - Error", request: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}}}, requestImpWrapper: []*ImpWrapper{{Imp: nil, impExt: &ImpExt{}}}, + expectedAccessed: true, expectedError: "ImpWrapper RebuildImp called on a nil Imp", }, { @@ -229,6 +241,7 @@ func TestRebuildImp(t *testing.T) { request: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}, {ID: "2"}}}, requestImpWrapper: []*ImpWrapper{{Imp: &openrtb2.Imp{ID: "1"}, impExt: &ImpExt{}}, {Imp: &openrtb2.Imp{ID: "2"}, impExt: &ImpExt{prebid: prebid, prebidDirty: true}}}, expectedRequest: openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}, {ID: "2", Ext: prebidJson}}}, + expectedAccessed: true, }, } @@ -247,6 +260,20 @@ func TestRebuildImp(t *testing.T) { assert.NoError(t, err, test.description) assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description) } + + if test.expectedAccessed && test.expectedError == "" { + bidRequestImps := make(map[string]*openrtb2.Imp, 0) + for i, v := range w.Imp { + bidRequestImps[v.ID] = &w.Imp[i] + } + wrapperImps := make(map[string]*openrtb2.Imp, 0) + for i, v := range w.impWrappers { + wrapperImps[v.ID] = w.impWrappers[i].Imp + } + for k := range bidRequestImps { + assert.Same(t, bidRequestImps[k], wrapperImps[k], test.description) + } + } } } @@ -705,7 +732,7 @@ func TestCloneUserExt(t *testing.T) { eids[0].UIDs[1].ID = "G2" eids[1].UIDs[0].AType = 0 eids[0].UIDs = append(eids[0].UIDs, openrtb2.UID{ID: "Z", AType: 2}) - eids = append(eids, openrtb2.EID{Source: "Blank"}) + eids = append(eids, openrtb2.EID{Source: "Blank"}) //nolint: ineffassign, staticcheck // this value of `eids` is never used (staticcheck) userExt.eids = nil }, }, @@ -767,13 +794,13 @@ func TestRebuildDeviceExt(t *testing.T) { { description: "Nil - Dirty", request: openrtb2.BidRequest{}, - requestDeviceExtWrapper: DeviceExt{prebid: &prebidContent1, prebidDirty: true}, - expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, + requestDeviceExtWrapper: DeviceExt{prebid: &prebidContent1, prebidDirty: true, cdep: "1", cdepDirty: true}, + expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"cdep":"1","prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, }, { description: "Nil - Dirty - No Change", request: openrtb2.BidRequest{}, - requestDeviceExtWrapper: DeviceExt{prebid: nil, prebidDirty: true}, + requestDeviceExtWrapper: DeviceExt{prebid: nil, prebidDirty: true, cdep: "", cdepDirty: true}, expectedRequest: openrtb2.BidRequest{}, }, { @@ -785,37 +812,37 @@ func TestRebuildDeviceExt(t *testing.T) { { description: "Empty - Dirty", request: openrtb2.BidRequest{Device: &openrtb2.Device{}}, - requestDeviceExtWrapper: DeviceExt{prebid: &prebidContent1, prebidDirty: true}, - expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, + requestDeviceExtWrapper: DeviceExt{prebid: &prebidContent1, prebidDirty: true, cdep: "1", cdepDirty: true}, + expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"cdep":"1","prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, }, { description: "Empty - Dirty - No Change", request: openrtb2.BidRequest{Device: &openrtb2.Device{}}, - requestDeviceExtWrapper: DeviceExt{prebid: nil, prebidDirty: true}, + requestDeviceExtWrapper: DeviceExt{prebid: nil, prebidDirty: true, cdep: "", cdepDirty: true}, expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{}}, }, { description: "Populated - Not Dirty", - request: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, + request: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"cdep":"1","prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, requestDeviceExtWrapper: DeviceExt{}, - expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, + expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"cdep":"1","prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, }, { description: "Populated - Dirty", - request: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, - requestDeviceExtWrapper: DeviceExt{prebid: &prebidContent2, prebidDirty: true}, - expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":2,"minheightperc":0}}}`)}}, + request: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"cdep":"1","prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, + requestDeviceExtWrapper: DeviceExt{prebid: &prebidContent2, prebidDirty: true, cdep: "2", cdepDirty: true}, + expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"cdep":"2","prebid":{"interstitial":{"minwidthperc":2,"minheightperc":0}}}`)}}, }, { description: "Populated - Dirty - No Change", - request: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, - requestDeviceExtWrapper: DeviceExt{prebid: &prebidContent1, prebidDirty: true}, - expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, + request: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"cdep":"1","prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, + requestDeviceExtWrapper: DeviceExt{prebid: &prebidContent1, prebidDirty: true, cdep: "1", cdepDirty: true}, + expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"cdep":"1","prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, }, { description: "Populated - Dirty - Cleared", - request: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, - requestDeviceExtWrapper: DeviceExt{prebid: nil, prebidDirty: true}, + request: openrtb2.BidRequest{Device: &openrtb2.Device{Ext: json.RawMessage(`{"cdep":"1","prebid":{"interstitial":{"minwidthperc":1,"minheightperc":0}}}`)}}, + requestDeviceExtWrapper: DeviceExt{prebid: nil, prebidDirty: true, cdep: "", cdepDirty: true}, expectedRequest: openrtb2.BidRequest{Device: &openrtb2.Device{}}, }, } @@ -1014,6 +1041,8 @@ func TestCloneDeviceExt(t *testing.T) { prebid: &ExtDevicePrebid{ Interstitial: &ExtDeviceInt{MinWidthPerc: 65.0, MinHeightPerc: 75.0}, }, + cdep: "1", + cdepDirty: true, }, devExtCopy: &DeviceExt{ ext: map[string]json.RawMessage{"A": json.RawMessage(`{}`), "B": json.RawMessage(`{"foo":"bar"}`)}, @@ -1021,6 +1050,8 @@ func TestCloneDeviceExt(t *testing.T) { prebid: &ExtDevicePrebid{ Interstitial: &ExtDeviceInt{MinWidthPerc: 65.0, MinHeightPerc: 75.0}, }, + cdep: "1", + cdepDirty: true, }, mutator: func(t *testing.T, devExt *DeviceExt) {}, }, @@ -1032,6 +1063,8 @@ func TestCloneDeviceExt(t *testing.T) { prebid: &ExtDevicePrebid{ Interstitial: &ExtDeviceInt{MinWidthPerc: 65.0, MinHeightPerc: 75.0}, }, + cdep: "1", + cdepDirty: true, }, devExtCopy: &DeviceExt{ ext: map[string]json.RawMessage{"A": json.RawMessage(`{}`), "B": json.RawMessage(`{"foo":"bar"}`)}, @@ -1039,6 +1072,8 @@ func TestCloneDeviceExt(t *testing.T) { prebid: &ExtDevicePrebid{ Interstitial: &ExtDeviceInt{MinWidthPerc: 65, MinHeightPerc: 75}, }, + cdep: "1", + cdepDirty: true, }, mutator: func(t *testing.T, devExt *DeviceExt) { devExt.ext["A"] = json.RawMessage(`"string"`) @@ -1047,6 +1082,8 @@ func TestCloneDeviceExt(t *testing.T) { devExt.prebid.Interstitial.MinHeightPerc = 55 devExt.prebid.Interstitial = &ExtDeviceInt{MinWidthPerc: 80} devExt.prebid = nil + devExt.cdep = "" + devExt.cdepDirty = true }, }, } @@ -1856,6 +1893,37 @@ func TestImpWrapperGetImpExt(t *testing.T) { } } +func TestImpWrapperSetImp(t *testing.T) { + origImps := []openrtb2.Imp{ + {ID: "imp1", TagID: "tag1"}, + {ID: "imp2", TagID: "tag2"}, + {ID: "imp3", TagID: "tag3"}, + } + expectedImps := []openrtb2.Imp{ + {ID: "imp1", TagID: "tag4", BidFloor: 0.5}, + {ID: "imp1.1", TagID: "tag2", BidFloor: 0.6}, + {ID: "imp2", TagID: "notag"}, + {ID: "imp3", TagID: "tag3"}, + } + rw := RequestWrapper{BidRequest: &openrtb2.BidRequest{Imp: origImps}} + iw := rw.GetImp() + rw.Imp[0].TagID = "tag4" + rw.Imp[0].BidFloor = 0.5 + iw[1] = &ImpWrapper{Imp: &expectedImps[1]} + *iw[2] = ImpWrapper{Imp: &expectedImps[2]} + iw = append(iw, &ImpWrapper{Imp: &expectedImps[3]}) + + rw.SetImp(iw) + assert.Equal(t, expectedImps, rw.BidRequest.Imp) + iw = rw.GetImp() + // Ensure that the wrapper pointers are in sync. + for i := range rw.BidRequest.Imp { + // Assert the pointers are in sync. + assert.Same(t, &rw.Imp[i], iw[i].Imp) + } + +} + func TestImpExtTid(t *testing.T) { impExt := &ImpExt{} @@ -2049,3 +2117,347 @@ func TestCloneImpExt(t *testing.T) { }) } } + +func TestRebuildRegExt(t *testing.T) { + strA := "a" + strB := "b" + + tests := []struct { + name string + request openrtb2.BidRequest + regExt RegExt + expectedRequest openrtb2.BidRequest + }{ + { + name: "req_regs_nil_-_not_dirty_-_no_change", + request: openrtb2.BidRequest{}, + regExt: RegExt{}, + expectedRequest: openrtb2.BidRequest{}, + }, + { + name: "req_regs_nil_-_dirty_and_different_-_change", + request: openrtb2.BidRequest{}, + regExt: RegExt{dsa: &ExtRegsDSA{Required: ptrutil.ToPtr[int8](1)}, dsaDirty: true, gdpr: ptrutil.ToPtr[int8](1), gdprDirty: true, usPrivacy: strA, usPrivacyDirty: true}, + expectedRequest: openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: json.RawMessage(`{"dsa":{"dsarequired":1},"gdpr":1,"us_privacy":"a"}`), + }, + }, + }, + { + name: "req_regs_ext_nil_-_not_dirty_-_no_change", + request: openrtb2.BidRequest{Regs: &openrtb2.Regs{}}, + regExt: RegExt{}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{}}, + }, + { + name: "req_regs_ext_nil_-_dirty_and_different_-_change", + request: openrtb2.BidRequest{Regs: &openrtb2.Regs{}}, + regExt: RegExt{dsa: &ExtRegsDSA{Required: ptrutil.ToPtr[int8](1)}, dsaDirty: true, gdpr: ptrutil.ToPtr[int8](1), gdprDirty: true, usPrivacy: strA, usPrivacyDirty: true}, + expectedRequest: openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ + Ext: json.RawMessage(`{"dsa":{"dsarequired":1},"gdpr":1,"us_privacy":"a"}`), + }, + }, + }, + { + name: "req_regs_dsa_populated_-_not_dirty_-_no_change", + request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"dsa":{"dsarequired":1}}`)}}, + regExt: RegExt{}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"dsa":{"dsarequired":1}}`)}}, + }, + { + name: "req_regs_dsa_populated_-_dirty_and_different-_change", + request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"dsa":{"dsarequired":1}}`)}}, + regExt: RegExt{dsa: &ExtRegsDSA{Required: ptrutil.ToPtr[int8](2)}, dsaDirty: true}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"dsa":{"dsarequired":2}}`)}}, + }, + { + name: "req_regs_dsa_populated_-_dirty_and_same_-_no_change", + request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"dsa":{"dsarequired":1}}`)}}, + regExt: RegExt{dsa: &ExtRegsDSA{Required: ptrutil.ToPtr[int8](1)}, dsaDirty: true}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"dsa":{"dsarequired":1}}`)}}, + }, + { + name: "req_regs_dsa_populated_-_dirty_and_nil_-_cleared", + request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{}`)}}, + regExt: RegExt{dsa: nil, dsaDirty: true}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{}}, + }, + { + name: "req_regs_gdpr_populated_-_not_dirty_-_no_change", + request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1}`)}}, + regExt: RegExt{}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1}`)}}, + }, + { + name: "req_regs_gdpr_populated_-_dirty_and_different-_change", + request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1}`)}}, + regExt: RegExt{gdpr: ptrutil.ToPtr[int8](0), gdprDirty: true}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":0}`)}}, + }, + { + name: "req_regs_gdpr_populated_-_dirty_and_same_-_no_change", + request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1}`)}}, + regExt: RegExt{gdpr: ptrutil.ToPtr[int8](1), gdprDirty: true}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr":1}`)}}, + }, + { + name: "req_regs_gdpr_populated_-_dirty_and_nil_-_cleared", + request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{}`)}}, + regExt: RegExt{gdpr: nil, gdprDirty: true}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{}}, + }, + { + name: "req_regs_usprivacy_populated_-_not_dirty_-_no_change", + request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"a"}`)}}, + regExt: RegExt{}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"a"}`)}}, + }, + { + name: "req_regs_usprivacy_populated_-_dirty_and_different-_change", + request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"a"}`)}}, + regExt: RegExt{usPrivacy: strB, usPrivacyDirty: true}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"b"}`)}}, + }, + { + name: "req_regs_usprivacy_populated_-_dirty_and_same_-_no_change", + request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"a"}`)}}, + regExt: RegExt{usPrivacy: strA, usPrivacyDirty: true}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"a"}`)}}, + }, + { + name: "req_regs_usprivacy_populated_-_dirty_and_nil_-_cleared", + request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"a"}`)}}, + regExt: RegExt{usPrivacy: "", usPrivacyDirty: true}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{}}, + }, + { + name: "req_regs_gpc_populated_-_not_dirty_-_no_change", + request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"a"}`)}}, + regExt: RegExt{}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"a"}`)}}, + }, + { + name: "req_regs_gpc_populated_-_dirty_and_different-_change", + request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"a"}`)}}, + regExt: RegExt{gpc: &strB, gpcDirty: true}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"b"}`)}}, + }, + { + name: "req_regs_gpc_populated_-_dirty_and_same_-_no_change", + request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"a"}`)}}, + regExt: RegExt{gpc: &strA, gpcDirty: true}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"a"}`)}}, + }, + { + name: "req_regs_gpc_populated_-_dirty_and_nil_-_cleared", + request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"a"}`)}}, + regExt: RegExt{gpc: nil, gpcDirty: true}, + expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{}}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.regExt.ext = make(map[string]json.RawMessage) + + w := RequestWrapper{BidRequest: &tt.request, regExt: &tt.regExt} + w.RebuildRequest() + assert.Equal(t, tt.expectedRequest, *w.BidRequest) + }) + } +} + +func TestRegExtUnmarshal(t *testing.T) { + tests := []struct { + name string + regExt *RegExt + extJson json.RawMessage + expectDSA *ExtRegsDSA + expectGDPR *int8 + expectGPC *string + expectUSPrivacy string + expectError bool + }{ + { + name: "RegExt.ext_not_empty_and_not_dirtyr", + regExt: &RegExt{ + ext: map[string]json.RawMessage{"dsa": json.RawMessage(`{}`)}, + }, + extJson: json.RawMessage{}, + expectError: false, + }, + { + name: "RegExt.ext_empty_and_dirty", + regExt: &RegExt{extDirty: true}, + extJson: json.RawMessage(`{"dsa":{"dsarequired":1}}`), + expectError: false, + }, + { + name: "nothing_to_unmarshal", + regExt: &RegExt{ + ext: map[string]json.RawMessage{}, + }, + extJson: json.RawMessage{}, + expectError: false, + }, + // DSA + { + name: "valid_dsa_json", + regExt: &RegExt{}, + extJson: json.RawMessage(`{"dsa":{"dsarequired":1}}`), + expectDSA: &ExtRegsDSA{ + Required: ptrutil.ToPtr[int8](1), + }, + expectError: false, + }, + { + name: "malformed_dsa_json", + regExt: &RegExt{}, + extJson: json.RawMessage(`{"dsa":{"dsarequired":""}}`), + expectDSA: &ExtRegsDSA{ + Required: ptrutil.ToPtr[int8](0), + }, + expectError: true, + }, + // GDPR + { + name: "valid_gdpr_json", + regExt: &RegExt{}, + extJson: json.RawMessage(`{"gdpr":1}`), + expectGDPR: ptrutil.ToPtr[int8](1), + expectError: false, + }, + { + name: "malformed_gdpr_json", + regExt: &RegExt{}, + extJson: json.RawMessage(`{"gdpr":""}`), + expectGDPR: ptrutil.ToPtr[int8](0), + expectError: true, + }, + // GPC + { + name: "valid_gpc_json", + regExt: &RegExt{}, + extJson: json.RawMessage(`{"gpc":"some_value"}`), + expectGPC: ptrutil.ToPtr("some_value"), + expectError: false, + }, + { + name: "malformed_gpc_json", + regExt: &RegExt{}, + extJson: json.RawMessage(`{"gpc":nill}`), + expectGPC: nil, + expectError: true, + }, + // us_privacy + { + name: "valid_usprivacy_json", + regExt: &RegExt{}, + extJson: json.RawMessage(`{"us_privacy":"consent"}`), + expectUSPrivacy: "consent", + expectError: false, + }, + { + name: "malformed_usprivacy_json", + regExt: &RegExt{}, + extJson: json.RawMessage(`{"us_privacy":1}`), + expectError: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.regExt.unmarshal(tt.extJson) + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.expectDSA, tt.regExt.dsa) + assert.Equal(t, tt.expectGDPR, tt.regExt.gdpr) + assert.Equal(t, tt.expectUSPrivacy, tt.regExt.usPrivacy) + }) + } +} + +func TestRegExtGetExtSetExt(t *testing.T) { + regExt := &RegExt{} + regExtJSON := regExt.GetExt() + assert.Equal(t, regExtJSON, map[string]json.RawMessage{}) + assert.False(t, regExt.Dirty()) + + rawJSON := map[string]json.RawMessage{ + "dsa": json.RawMessage(`{}`), + "gdpr": json.RawMessage(`1`), + "usprivacy": json.RawMessage(`"consent"`), + } + regExt.SetExt(rawJSON) + assert.True(t, regExt.Dirty()) + + regExtJSON = regExt.GetExt() + assert.Equal(t, regExtJSON, rawJSON) + assert.NotSame(t, regExtJSON, rawJSON) +} + +func TestRegExtGetDSASetDSA(t *testing.T) { + regExt := &RegExt{} + regExtDSA := regExt.GetDSA() + assert.Nil(t, regExtDSA) + assert.False(t, regExt.Dirty()) + + dsa := &ExtRegsDSA{ + Required: ptrutil.ToPtr[int8](2), + } + regExt.SetDSA(dsa) + assert.True(t, regExt.Dirty()) + + regExtDSA = regExt.GetDSA() + assert.Equal(t, regExtDSA, dsa) + assert.NotSame(t, regExtDSA, dsa) +} + +func TestRegExtGetUSPrivacySetUSPrivacy(t *testing.T) { + regExt := &RegExt{} + regExtUSPrivacy := regExt.GetUSPrivacy() + assert.Equal(t, regExtUSPrivacy, "") + assert.False(t, regExt.Dirty()) + + usprivacy := "consent" + regExt.SetUSPrivacy(usprivacy) + assert.True(t, regExt.Dirty()) + + regExtUSPrivacy = regExt.GetUSPrivacy() + assert.Equal(t, regExtUSPrivacy, usprivacy) + assert.NotSame(t, regExtUSPrivacy, usprivacy) +} + +func TestRegExtGetGDPRSetGDPR(t *testing.T) { + regExt := &RegExt{} + regExtGDPR := regExt.GetGDPR() + assert.Nil(t, regExtGDPR) + assert.False(t, regExt.Dirty()) + + gdpr := ptrutil.ToPtr[int8](1) + regExt.SetGDPR(gdpr) + assert.True(t, regExt.Dirty()) + + regExtGDPR = regExt.GetGDPR() + assert.Equal(t, regExtGDPR, gdpr) + assert.NotSame(t, regExtGDPR, gdpr) +} + +func TestRegExtGetGPCSetGPC(t *testing.T) { + regExt := &RegExt{} + regExtGPC := regExt.GetGPC() + assert.Nil(t, regExtGPC) + assert.False(t, regExt.Dirty()) + + gpc := ptrutil.ToPtr("Gpc") + regExt.SetGPC(gpc) + assert.True(t, regExt.Dirty()) + + regExtGPC = regExt.GetGPC() + assert.Equal(t, regExtGPC, gpc) + assert.NotSame(t, regExtGPC, gpc) +} diff --git a/openrtb_ext/response.go b/openrtb_ext/response.go index 8e9f36e8484..449ff939bf5 100644 --- a/openrtb_ext/response.go +++ b/openrtb_ext/response.go @@ -3,8 +3,8 @@ package openrtb_ext import ( "encoding/json" - "github.com/prebid/openrtb/v19/adcom1" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/adcom1" + "github.com/prebid/openrtb/v20/openrtb2" ) // ExtBidResponse defines the contract for bidresponse.ext @@ -103,9 +103,9 @@ const ( ) // NonBidObject 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 Price float64 `json:"price,omitempty"` ADomain []string `json:"adomain,omitempty"` CatTax adcom1.CategoryTaxonomy `json:"cattax,omitempty"` @@ -116,6 +116,7 @@ type NonBidObject struct { Dur int64 `json:"dur,omitempty"` MType openrtb2.MarkupType `json:"mtype,omitempty"` + // Custom Fields OriginalBidCPM float64 `json:"origbidcpm,omitempty"` OriginalBidCur string `json:"origbidcur,omitempty"` } @@ -131,14 +132,14 @@ type NonBidExt struct { // 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"` + ImpId string `json:"impid"` + StatusCode int `json:"statuscode"` + Ext *NonBidExt `json:"ext,omitempty"` } // SeatNonBid is collection of NonBid objects with seat information type SeatNonBid struct { NonBid []NonBid `json:"nonbid"` Seat string `json:"seat"` - Ext json.RawMessage `json:"ext"` + Ext json.RawMessage `json:"ext,omitempty"` } diff --git a/openrtb_ext/site_test.go b/openrtb_ext/site_test.go index 7a7140282f2..e06c0e67bd4 100644 --- a/openrtb_ext/site_test.go +++ b/openrtb_ext/site_test.go @@ -3,8 +3,8 @@ package openrtb_ext_test import ( "testing" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/openrtb_ext/source.go b/openrtb_ext/source.go index 78112ec7668..dd2a4b0ddc8 100644 --- a/openrtb_ext/source.go +++ b/openrtb_ext/source.go @@ -1,6 +1,6 @@ package openrtb_ext -import "github.com/prebid/openrtb/v19/openrtb2" +import "github.com/prebid/openrtb/v20/openrtb2" // ExtSource defines the contract for bidrequest.source.ext type ExtSource struct { diff --git a/openrtb_ext/supplyChain.go b/openrtb_ext/supplyChain.go index 6f023542dfb..0051d647ff9 100644 --- a/openrtb_ext/supplyChain.go +++ b/openrtb_ext/supplyChain.go @@ -1,8 +1,8 @@ package openrtb_ext import ( - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/util/ptrutil" ) func cloneSupplyChain(schain *openrtb2.SupplyChain) *openrtb2.SupplyChain { diff --git a/openrtb_ext/supplyChain_test.go b/openrtb_ext/supplyChain_test.go index 12fd5c337fb..1adfaf8f62b 100644 --- a/openrtb_ext/supplyChain_test.go +++ b/openrtb_ext/supplyChain_test.go @@ -4,8 +4,8 @@ import ( "encoding/json" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" ) diff --git a/openrtb_ext/user.go b/openrtb_ext/user.go index 19c6bbc9a6c..422c3f85e7b 100644 --- a/openrtb_ext/user.go +++ b/openrtb_ext/user.go @@ -4,7 +4,7 @@ import ( "strconv" "strings" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" ) // ExtUser defines the contract for bidrequest.user.ext diff --git a/ortb/clone.go b/ortb/clone.go index c0e5a4ddada..07eaf2886b7 100644 --- a/ortb/clone.go +++ b/ortb/clone.go @@ -1,47 +1,60 @@ package ortb import ( - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/ptrutil" - "github.com/prebid/prebid-server/util/sliceutil" + "slices" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/util/ptrutil" ) -func CloneApp(s *openrtb2.App) *openrtb2.App { +func CloneDataSlice(s []openrtb2.Data) []openrtb2.Data { if s == nil { return nil } - // Shallow Copy (Value Fields) - c := *s + c := make([]openrtb2.Data, len(s)) + for i, d := range s { + c[i] = CloneData(d) + } + + return c +} + +func CloneData(s openrtb2.Data) openrtb2.Data { + // Shallow Copy (Value Fields) Occurred By Passing Argument By Value + // - Implicitly created by the cloned array. // Deep Copy (Pointers) - c.Cat = sliceutil.Clone(s.Cat) - c.SectionCat = sliceutil.Clone(s.SectionCat) - c.PageCat = sliceutil.Clone(s.PageCat) - c.Publisher = ClonePublisher(s.Publisher) - c.Content = CloneContent(s.Content) - c.KwArray = sliceutil.Clone(s.KwArray) - c.Ext = sliceutil.Clone(s.Ext) + s.Segment = CloneSegmentSlice(s.Segment) + s.Ext = slices.Clone(s.Ext) - return &c + return s } -func ClonePublisher(s *openrtb2.Publisher) *openrtb2.Publisher { +func CloneSegmentSlice(s []openrtb2.Segment) []openrtb2.Segment { if s == nil { return nil } - // Shallow Copy (Value Fields) - c := *s + c := make([]openrtb2.Segment, len(s)) + for i, d := range s { + c[i] = CloneSegment(d) + } + + return c +} + +func CloneSegment(s openrtb2.Segment) openrtb2.Segment { + // Shallow Copy (Value Fields) Occurred By Passing Argument By Value + // - Implicitly created by the cloned array. // Deep Copy (Pointers) - c.Cat = sliceutil.Clone(s.Cat) - c.Ext = sliceutil.Clone(s.Ext) + s.Ext = slices.Clone(s.Ext) - return &c + return s } -func CloneContent(s *openrtb2.Content) *openrtb2.Content { +func CloneUser(s *openrtb2.User) *openrtb2.User { if s == nil { return nil } @@ -50,20 +63,16 @@ func CloneContent(s *openrtb2.Content) *openrtb2.Content { c := *s // Deep Copy (Pointers) - c.Producer = CloneProducer(s.Producer) - c.Cat = sliceutil.Clone(s.Cat) - c.ProdQ = ptrutil.Clone(s.ProdQ) - c.VideoQuality = ptrutil.Clone(s.VideoQuality) - c.KwArray = sliceutil.Clone(s.KwArray) + c.KwArray = slices.Clone(s.KwArray) + c.Geo = CloneGeo(s.Geo) c.Data = CloneDataSlice(s.Data) - c.Network = CloneNetwork(s.Network) - c.Channel = CloneChannel(s.Channel) - c.Ext = sliceutil.Clone(s.Ext) + c.EIDs = CloneEIDSlice(s.EIDs) + c.Ext = slices.Clone(s.Ext) return &c } -func CloneProducer(s *openrtb2.Producer) *openrtb2.Producer { +func CloneDevice(s *openrtb2.Device) *openrtb2.Device { if s == nil { return nil } @@ -72,72 +81,69 @@ func CloneProducer(s *openrtb2.Producer) *openrtb2.Producer { c := *s // Deep Copy (Pointers) - c.Cat = sliceutil.Clone(s.Cat) - c.Ext = sliceutil.Clone(s.Ext) + c.Geo = CloneGeo(s.Geo) + c.DNT = ptrutil.Clone(s.DNT) + c.Lmt = ptrutil.Clone(s.Lmt) + c.SUA = CloneUserAgent(s.SUA) + c.JS = ptrutil.Clone(s.JS) + c.GeoFetch = ptrutil.Clone(s.GeoFetch) + c.ConnectionType = ptrutil.Clone(s.ConnectionType) + c.Ext = slices.Clone(s.Ext) return &c } -func CloneDataSlice(s []openrtb2.Data) []openrtb2.Data { +func CloneUserAgent(s *openrtb2.UserAgent) *openrtb2.UserAgent { if s == nil { return nil } - c := make([]openrtb2.Data, len(s)) - for i, d := range s { - c[i] = CloneData(d) - } - - return c -} - -func CloneData(s openrtb2.Data) openrtb2.Data { - // Shallow Copy (Value Fields) Occurred By Passing Argument By Value + // Shallow Copy (Value Fields) + c := *s // Deep Copy (Pointers) - s.Segment = CloneSegmentSlice(s.Segment) - s.Ext = sliceutil.Clone(s.Ext) + c.Browsers = CloneBrandVersionSlice(s.Browsers) + c.Platform = CloneBrandVersion(s.Platform) - return s + if s.Mobile != nil { + mobileCopy := *s.Mobile + c.Mobile = &mobileCopy + } + s.Ext = slices.Clone(s.Ext) + + return &c } -func CloneSegmentSlice(s []openrtb2.Segment) []openrtb2.Segment { +func CloneBrandVersionSlice(s []openrtb2.BrandVersion) []openrtb2.BrandVersion { if s == nil { return nil } - c := make([]openrtb2.Segment, len(s)) + c := make([]openrtb2.BrandVersion, len(s)) for i, d := range s { - c[i] = CloneSegment(d) + bv := CloneBrandVersion(&d) + c[i] = *bv } return c } -func CloneSegment(s openrtb2.Segment) openrtb2.Segment { - // Shallow Copy (Value Fields) Occurred By Passing Argument By Value - - // Deep Copy (Pointers) - s.Ext = sliceutil.Clone(s.Ext) - - return s -} - -func CloneNetwork(s *openrtb2.Network) *openrtb2.Network { +func CloneBrandVersion(s *openrtb2.BrandVersion) *openrtb2.BrandVersion { if s == nil { return nil } - // Shallow Copy (Value Fields) + // Shallow Copy (Value Fields) Occurred By Passing Argument By Value c := *s // Deep Copy (Pointers) - c.Ext = sliceutil.Clone(s.Ext) + c.Version = slices.Clone(s.Version) + c.Ext = slices.Clone(s.Ext) return &c } -func CloneChannel(s *openrtb2.Channel) *openrtb2.Channel { +func CloneSource(s *openrtb2.Source) *openrtb2.Source { if s == nil { return nil } @@ -146,12 +152,14 @@ func CloneChannel(s *openrtb2.Channel) *openrtb2.Channel { c := *s // Deep Copy (Pointers) - c.Ext = sliceutil.Clone(s.Ext) + c.FD = ptrutil.Clone(s.FD) + c.SChain = CloneSChain(s.SChain) + c.Ext = slices.Clone(s.Ext) return &c } -func CloneSite(s *openrtb2.Site) *openrtb2.Site { +func CloneSChain(s *openrtb2.SupplyChain) *openrtb2.SupplyChain { if s == nil { return nil } @@ -160,33 +168,34 @@ func CloneSite(s *openrtb2.Site) *openrtb2.Site { c := *s // Deep Copy (Pointers) - c.Cat = sliceutil.Clone(s.Cat) - c.SectionCat = sliceutil.Clone(s.SectionCat) - c.PageCat = sliceutil.Clone(s.PageCat) - c.Publisher = ClonePublisher(s.Publisher) - c.Content = CloneContent(s.Content) - c.KwArray = sliceutil.Clone(s.KwArray) - c.Ext = sliceutil.Clone(s.Ext) + c.Nodes = CloneSupplyChainNodes(s.Nodes) + c.Ext = slices.Clone(s.Ext) return &c } -func CloneUser(s *openrtb2.User) *openrtb2.User { +func CloneSupplyChainNodes(s []openrtb2.SupplyChainNode) []openrtb2.SupplyChainNode { if s == nil { return nil } - // Shallow Copy (Value Fields) - c := *s + c := make([]openrtb2.SupplyChainNode, len(s)) + for i, d := range s { + c[i] = CloneSupplyChainNode(d) + } + + return c +} + +func CloneSupplyChainNode(s openrtb2.SupplyChainNode) openrtb2.SupplyChainNode { + // Shallow Copy (Value Fields) Occurred By Passing Argument By Value + // - Implicitly created by the cloned array. // Deep Copy (Pointers) - c.KwArray = sliceutil.Clone(s.KwArray) - c.Geo = CloneGeo(s.Geo) - c.Data = CloneDataSlice(s.Data) - c.EIDs = CloneEIDSlice(s.EIDs) - c.Ext = sliceutil.Clone(s.Ext) + s.HP = ptrutil.Clone(s.HP) + s.Ext = slices.Clone(s.Ext) - return &c + return s } func CloneGeo(s *openrtb2.Geo) *openrtb2.Geo { @@ -198,7 +207,9 @@ func CloneGeo(s *openrtb2.Geo) *openrtb2.Geo { c := *s // Deep Copy (Pointers) - c.Ext = sliceutil.Clone(s.Ext) + c.Lat = ptrutil.Clone(s.Lat) + c.Lon = ptrutil.Clone(s.Lon) + c.Ext = slices.Clone(s.Ext) return &c } @@ -218,10 +229,11 @@ func CloneEIDSlice(s []openrtb2.EID) []openrtb2.EID { func CloneEID(s openrtb2.EID) openrtb2.EID { // Shallow Copy (Value Fields) Occurred By Passing Argument By Value + // - Implicitly created by the cloned array. // Deep Copy (Pointers) s.UIDs = CloneUIDSlice(s.UIDs) - s.Ext = sliceutil.Clone(s.Ext) + s.Ext = slices.Clone(s.Ext) return s } @@ -241,14 +253,32 @@ func CloneUIDSlice(s []openrtb2.UID) []openrtb2.UID { func CloneUID(s openrtb2.UID) openrtb2.UID { // Shallow Copy (Value Fields) Occurred By Passing Argument By Value + // - Implicitly created by the cloned array. // Deep Copy (Pointers) - s.Ext = sliceutil.Clone(s.Ext) + s.Ext = slices.Clone(s.Ext) return s } -func CloneDOOH(s *openrtb2.DOOH) *openrtb2.DOOH { +// CloneBidRequestPartial performs a deep clone of just the bid request device, user, and source fields. +func CloneBidRequestPartial(s *openrtb2.BidRequest) *openrtb2.BidRequest { + if s == nil { + return nil + } + + // Shallow Copy (Value Fields) + c := *s + + // Deep Copy (Pointers) - PARTIAL CLONE + c.Device = CloneDevice(s.Device) + c.User = CloneUser(s.User) + c.Source = CloneSource(s.Source) + + return &c +} + +func CloneRegs(s *openrtb2.Regs) *openrtb2.Regs { if s == nil { return nil } @@ -257,11 +287,9 @@ func CloneDOOH(s *openrtb2.DOOH) *openrtb2.DOOH { c := *s // Deep Copy (Pointers) - c.VenueType = sliceutil.Clone(s.VenueType) - c.VenueTypeTax = ptrutil.Clone(s.VenueTypeTax) - c.Publisher = ClonePublisher(s.Publisher) - c.Content = CloneContent(s.Content) - c.Ext = sliceutil.Clone(s.Ext) + c.GDPR = ptrutil.Clone(s.GDPR) + c.GPPSID = slices.Clone(s.GPPSID) + c.Ext = slices.Clone(s.Ext) return &c } diff --git a/ortb/clone_test.go b/ortb/clone_test.go index 24e43bda1e5..14bdc0bb194 100644 --- a/ortb/clone_test.go +++ b/ortb/clone_test.go @@ -5,532 +5,544 @@ import ( "reflect" "testing" - "github.com/prebid/openrtb/v19/adcom1" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/openrtb/v20/adcom1" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" ) -func TestCloneApp(t *testing.T) { +func TestCloneDataSlice(t *testing.T) { t.Run("nil", func(t *testing.T) { - result := CloneApp(nil) + result := CloneDataSlice(nil) assert.Nil(t, result) }) t.Run("empty", func(t *testing.T) { - given := &openrtb2.App{} - result := CloneApp(given) - assert.Equal(t, given, result) + given := []openrtb2.Data{} + result := CloneDataSlice(given) + assert.Empty(t, result) assert.NotSame(t, given, result) }) + t.Run("one", func(t *testing.T) { + given := []openrtb2.Data{ + {ID: "1", Ext: json.RawMessage(`{"anyField":1}`)}, + } + result := CloneDataSlice(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given[0], result[0], "item-pointer") + assert.NotSame(t, given[0].Ext, result[0].Ext, "item-pointer-ext") + }) + + t.Run("many", func(t *testing.T) { + given := []openrtb2.Data{ + {ID: "1", Ext: json.RawMessage(`{"anyField":1}`)}, + {ID: "2", Ext: json.RawMessage(`{"anyField":2}`)}, + } + result := CloneDataSlice(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given[0], result[0], "item0-pointer") + assert.NotSame(t, given[0].Ext, result[0].Ext, "item0-pointer-ext") + assert.NotSame(t, given[1], result[1], "item1-pointer") + assert.NotSame(t, given[1].Ext, result[1].Ext, "item1-pointer-ext") + }) +} + +func TestCloneData(t *testing.T) { t.Run("populated", func(t *testing.T) { - given := &openrtb2.App{ - ID: "anyID", - Name: "anyName", - Bundle: "anyBundle", - Domain: "anyDomain", - StoreURL: "anyStoreURL", - CatTax: adcom1.CatTaxIABContent10, - Cat: []string{"cat1"}, - SectionCat: []string{"sectionCat1"}, - PageCat: []string{"pageCat1"}, - Ver: "anyVer", - PrivacyPolicy: 1, - Paid: 2, - Publisher: &openrtb2.Publisher{ID: "anyPublisher", Ext: json.RawMessage(`{"publisher":1}`)}, - Content: &openrtb2.Content{ID: "anyContent", Ext: json.RawMessage(`{"content":1}`)}, - Keywords: "anyKeywords", - KwArray: []string{"key1"}, - InventoryPartnerDomain: "anyInventoryPartnerDomain", - Ext: json.RawMessage(`{"anyField":1}`), + given := openrtb2.Data{ + ID: "anyID", + Name: "anyName", + Segment: []openrtb2.Segment{{ID: "1", Ext: json.RawMessage(`{"anyField":1}`)}}, + Ext: json.RawMessage(`{"anyField":1}`), } - result := CloneApp(given) + result := CloneData(given) assert.Equal(t, given, result, "equality") assert.NotSame(t, given, result, "pointer") - assert.NotSame(t, given.Cat, result.Cat, "cat") - assert.NotSame(t, given.SectionCat, result.SectionCat, "sectioncat") - assert.NotSame(t, given.PageCat, result.PageCat, "pagecat") - assert.NotSame(t, given.Publisher, result.Publisher, "publisher") - assert.NotSame(t, given.Publisher.Ext, result.Publisher.Ext, "publisher-ext") - assert.NotSame(t, given.Content, result.Content, "content") - assert.NotSame(t, given.Content.Ext, result.Content.Ext, "content-ext") - assert.NotSame(t, given.KwArray, result.KwArray, "kwarray") + assert.NotSame(t, given.Segment, result.Segment, "segment") + assert.NotSame(t, given.Segment[0], result.Segment[0], "segment-item") + assert.NotSame(t, given.Segment[0].Ext, result.Segment[0].Ext, "segment-item-ext") assert.NotSame(t, given.Ext, result.Ext, "ext") }) t.Run("assumptions", func(t *testing.T) { - assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.App{})), + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Data{})), []string{ - "Cat", - "SectionCat", - "PageCat", - "Publisher", - "Content", - "KwArray", + "Segment", "Ext", }) }) } -func TestClonePublisher(t *testing.T) { +func TestCloneSegmentSlice(t *testing.T) { t.Run("nil", func(t *testing.T) { - result := ClonePublisher(nil) + result := CloneSegmentSlice(nil) assert.Nil(t, result) }) t.Run("empty", func(t *testing.T) { - given := &openrtb2.Publisher{} - result := ClonePublisher(given) - assert.Equal(t, given, result) + given := []openrtb2.Segment{} + result := CloneSegmentSlice(given) + assert.Empty(t, result) assert.NotSame(t, given, result) }) + t.Run("one", func(t *testing.T) { + given := []openrtb2.Segment{ + {ID: "1", Ext: json.RawMessage(`{"anyField":1}`)}, + } + result := CloneSegmentSlice(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given[0], result[0], "item-pointer") + assert.NotSame(t, given[0].Ext, result[0].Ext, "item-pointer-ext") + }) + + t.Run("many", func(t *testing.T) { + given := []openrtb2.Segment{ + {Ext: json.RawMessage(`{"anyField":1}`)}, + {Ext: json.RawMessage(`{"anyField":2}`)}, + } + result := CloneSegmentSlice(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given[0], result[0], "item0-pointer") + assert.NotSame(t, given[0].Ext, result[0].Ext, "item0-pointer-ext") + assert.NotSame(t, given[1], result[1], "item1-pointer") + assert.NotSame(t, given[1].Ext, result[1].Ext, "item1-pointer-ext") + }) +} + +func TestCloneSegment(t *testing.T) { t.Run("populated", func(t *testing.T) { - given := &openrtb2.Publisher{ - ID: "anyID", - Name: "anyName", - CatTax: adcom1.CatTaxIABContent20, - Cat: []string{"cat1"}, - Domain: "anyDomain", - Ext: json.RawMessage(`{"anyField":1}`), + given := openrtb2.Segment{ + ID: "anyID", + Name: "anyName", + Value: "anyValue", + Ext: json.RawMessage(`{"anyField":1}`), } - result := ClonePublisher(given) + result := CloneSegment(given) assert.Equal(t, given, result, "equality") assert.NotSame(t, given, result, "pointer") - assert.NotSame(t, given.Cat, result.Cat, "cat") assert.NotSame(t, given.Ext, result.Ext, "ext") }) t.Run("assumptions", func(t *testing.T) { - assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Publisher{})), + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Segment{})), []string{ - "Cat", "Ext", }) }) } -func TestCloneContent(t *testing.T) { +func TestCloneUser(t *testing.T) { t.Run("nil", func(t *testing.T) { - result := CloneContent(nil) + result := CloneUser(nil) assert.Nil(t, result) }) t.Run("empty", func(t *testing.T) { - given := &openrtb2.Content{} - result := CloneContent(given) - assert.Equal(t, given, result) + given := &openrtb2.User{} + result := CloneUser(given) + assert.Empty(t, result) assert.NotSame(t, given, result) }) t.Run("populated", func(t *testing.T) { - given := &openrtb2.Content{ - ID: "anyID", - Episode: 1, - Title: "anyTitle", - Series: "anySeries", - Season: "anySeason", - Artist: "anyArtist", - Genre: "anyGenre", - Album: "anyAlbum", - ISRC: "anyIsrc", - Producer: &openrtb2.Producer{ID: "anyID", Cat: []string{"anyCat"}}, - URL: "anyUrl", - CatTax: adcom1.CatTaxIABContent10, - Cat: []string{"cat1"}, - ProdQ: ptrutil.ToPtr(adcom1.ProductionProsumer), - VideoQuality: ptrutil.ToPtr(adcom1.ProductionProfessional), - Context: adcom1.ContentApp, - ContentRating: "anyContentRating", - UserRating: "anyUserRating", - QAGMediaRating: adcom1.MediaRatingAll, - Keywords: "anyKeywords", - KwArray: []string{"key1"}, - LiveStream: 2, - SourceRelationship: 3, - Len: 4, - Language: "anyLanguage", - LangB: "anyLangB", - Embeddable: 5, - Data: []openrtb2.Data{{ID: "1", Ext: json.RawMessage(`{"data":1}`)}}, - Network: &openrtb2.Network{ID: "anyNetwork", Ext: json.RawMessage(`{"network":1}`)}, - Channel: &openrtb2.Channel{ID: "anyChannel", Ext: json.RawMessage(`{"channel":1}`)}, - Ext: json.RawMessage(`{"anyField":1}`), + given := &openrtb2.User{ + ID: "anyID", + BuyerUID: "anyBuyerUID", + Yob: 1, + Gender: "anyGender", + Keywords: "anyKeywords", + KwArray: []string{"key1"}, + CustomData: "anyCustomData", + Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(1.2), Lon: ptrutil.ToPtr(2.3), Ext: json.RawMessage(`{"geo":1}`)}, + Data: []openrtb2.Data{{ID: "1", Ext: json.RawMessage(`{"data":1}`)}}, + Consent: "anyConsent", + EIDs: []openrtb2.EID{{Source: "1", Ext: json.RawMessage(`{"eid":1}`)}}, + Ext: json.RawMessage(`{"anyField":1}`), } - result := CloneContent(given) + result := CloneUser(given) assert.Equal(t, given, result, "equality") assert.NotSame(t, given, result, "pointer") - assert.NotSame(t, given.Producer, result.Producer, "producer") - assert.NotSame(t, given.Producer.Cat, result.Producer.Cat, "producer-cat") - assert.NotSame(t, given.Cat, result.Cat, "cat") - assert.NotSame(t, given.ProdQ, result.ProdQ, "prodq") - assert.NotSame(t, given.VideoQuality, result.VideoQuality, "videoquality") - assert.NotSame(t, given.KwArray, result.KwArray, "kwarray") - assert.NotSame(t, given.Data, result.Data, "data") + assert.NotSame(t, given.KwArray, result.KwArray, "cat") + assert.NotSame(t, given.Geo, result.Geo, "geo") + assert.NotSame(t, given.Geo.Ext, result.Geo.Ext, "geo-ext") assert.NotSame(t, given.Data[0], result.Data[0], "data-item") assert.NotSame(t, given.Data[0].Ext, result.Data[0].Ext, "data-item-ext") - assert.NotSame(t, given.Network, result.Network, "network") - assert.NotSame(t, given.Network.Ext, result.Network.Ext, "network-ext") - assert.NotSame(t, given.Channel, result.Channel, "channel") - assert.NotSame(t, given.Channel.Ext, result.Channel.Ext, "channel-ext") + assert.NotSame(t, given.EIDs[0], result.EIDs[0], "eids-item") + assert.NotSame(t, given.EIDs[0].Ext, result.EIDs[0].Ext, "eids-item-ext") assert.NotSame(t, given.Ext, result.Ext, "ext") }) t.Run("assumptions", func(t *testing.T) { - assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Content{})), + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.User{})), []string{ - "Producer", - "Cat", - "ProdQ", - "VideoQuality", "KwArray", + "Geo", "Data", - "Network", - "Channel", + "EIDs", "Ext", }) }) } -func TestCloneProducer(t *testing.T) { +func TestCloneDevice(t *testing.T) { t.Run("nil", func(t *testing.T) { - result := CloneProducer(nil) + result := CloneDevice(nil) assert.Nil(t, result) }) t.Run("empty", func(t *testing.T) { - given := &openrtb2.Producer{} - result := CloneProducer(given) - assert.Equal(t, given, result) + given := &openrtb2.Device{} + result := CloneDevice(given) + assert.Empty(t, result) assert.NotSame(t, given, result) }) t.Run("populated", func(t *testing.T) { - given := &openrtb2.Producer{ - ID: "anyID", - Name: "anyName", - CatTax: adcom1.CatTaxIABContent20, - Cat: []string{"cat1"}, - Domain: "anyDomain", - Ext: json.RawMessage(`{"anyField":1}`), + var n int8 = 1 + np := &n + ct := adcom1.ConnectionWIFI + + given := &openrtb2.Device{ + Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(1.2), Lon: ptrutil.ToPtr(2.3), Ext: json.RawMessage(`{"geo":1}`)}, + DNT: np, + Lmt: np, + UA: "UserAgent", + SUA: &openrtb2.UserAgent{Mobile: np, Model: "iPad"}, + IP: "127.0.0.1", + IPv6: "2001::", + DeviceType: adcom1.DeviceTablet, + Make: "Apple", + Model: "iPad", + OS: "macOS", + OSV: "1.2.3", + HWV: "mini", + H: 20, + W: 30, + PPI: 100, + PxRatio: 200, + JS: ptrutil.ToPtr[int8](2), + GeoFetch: ptrutil.ToPtr[int8](4), + FlashVer: "1.22.33", + Language: "En", + LangB: "ENG", + Carrier: "AT&T", + MCCMNC: "111-222", + ConnectionType: &ct, + IFA: "IFA", + DIDSHA1: "DIDSHA1", + DIDMD5: "DIDMD5", + DPIDSHA1: "DPIDSHA1", + DPIDMD5: "DPIDMD5", + MACSHA1: "MACSHA1", + MACMD5: "MACMD5", + Ext: json.RawMessage(`{"anyField":1}`), } - result := CloneProducer(given) + result := CloneDevice(given) assert.Equal(t, given, result, "equality") assert.NotSame(t, given, result, "pointer") - assert.NotSame(t, given.Cat, result.Cat, "cat") + assert.NotSame(t, given.Geo, result.Geo, "geo") + assert.NotSame(t, given.Geo.Ext, result.Geo.Ext, "geo-ext") + assert.NotSame(t, given.DNT, result.DNT, "dnt") + assert.NotSame(t, given.Lmt, result.Lmt, "lmt") + assert.NotSame(t, given.SUA, result.SUA, "sua") + assert.NotSame(t, given.JS, result.JS, "js") + assert.NotSame(t, given.GeoFetch, result.GeoFetch, "geofetch") + assert.NotSame(t, given.ConnectionType, result.ConnectionType, "connectionType") assert.NotSame(t, given.Ext, result.Ext, "ext") }) t.Run("assumptions", func(t *testing.T) { - assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Producer{})), + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Device{})), []string{ - "Cat", + "Geo", + "DNT", + "Lmt", + "SUA", + "JS", + "GeoFetch", + "ConnectionType", "Ext", }) }) } -func TestCloneDataSlice(t *testing.T) { +func TestCloneUserAgent(t *testing.T) { t.Run("nil", func(t *testing.T) { - result := CloneDataSlice(nil) + result := CloneUserAgent(nil) assert.Nil(t, result) }) t.Run("empty", func(t *testing.T) { - given := []openrtb2.Data{} - result := CloneDataSlice(given) + given := &openrtb2.UserAgent{} + result := CloneUserAgent(given) assert.Empty(t, result) assert.NotSame(t, given, result) }) - t.Run("one", func(t *testing.T) { - given := []openrtb2.Data{ - {ID: "1", Ext: json.RawMessage(`{"anyField":1}`)}, - } - result := CloneDataSlice(given) - assert.Equal(t, given, result, "equality") - assert.NotSame(t, given[0], result[0], "item-pointer") - assert.NotSame(t, given[0].Ext, result[0].Ext, "item-pointer-ext") - }) - - t.Run("many", func(t *testing.T) { - given := []openrtb2.Data{ - {ID: "1", Ext: json.RawMessage(`{"anyField":1}`)}, - {ID: "2", Ext: json.RawMessage(`{"anyField":2}`)}, - } - result := CloneDataSlice(given) - assert.Equal(t, given, result, "equality") - assert.NotSame(t, given[0], result[0], "item0-pointer") - assert.NotSame(t, given[0].Ext, result[0].Ext, "item0-pointer-ext") - assert.NotSame(t, given[1], result[1], "item1-pointer") - assert.NotSame(t, given[1].Ext, result[1].Ext, "item1-pointer-ext") - }) -} - -func TestCloneData(t *testing.T) { t.Run("populated", func(t *testing.T) { - given := openrtb2.Data{ - ID: "anyID", - Name: "anyName", - Segment: []openrtb2.Segment{{ID: "1", Ext: json.RawMessage(`{"anyField":1}`)}}, - Ext: json.RawMessage(`{"anyField":1}`), + var n int8 = 1 + np := &n + + given := &openrtb2.UserAgent{ + Browsers: []openrtb2.BrandVersion{{Brand: "Apple"}}, + Platform: &openrtb2.BrandVersion{Brand: "Apple"}, + Mobile: np, + Architecture: "X86", + Bitness: "64", + Model: "iPad", + Source: adcom1.UASourceLowEntropy, + Ext: json.RawMessage(`{"anyField":1}`), } - result := CloneData(given) + result := CloneUserAgent(given) assert.Equal(t, given, result, "equality") assert.NotSame(t, given, result, "pointer") - assert.NotSame(t, given.Segment, result.Segment, "segment") - assert.NotSame(t, given.Segment[0], result.Segment[0], "segment-item") - assert.NotSame(t, given.Segment[0].Ext, result.Segment[0].Ext, "segment-item-ext") + assert.NotSame(t, given.Browsers, result.Browsers, "browsers") + assert.NotSame(t, given.Platform, result.Platform, "platform") + assert.NotSame(t, given.Mobile, result.Mobile, "mobile") + assert.NotSame(t, given.Architecture, result.Architecture, "architecture") assert.NotSame(t, given.Ext, result.Ext, "ext") }) t.Run("assumptions", func(t *testing.T) { - assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Data{})), + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.UserAgent{})), []string{ - "Segment", + "Browsers", + "Platform", + "Mobile", "Ext", }) }) } -func TestCloneSegmentSlice(t *testing.T) { +func TestCloneBrandVersionSlice(t *testing.T) { t.Run("nil", func(t *testing.T) { - result := CloneSegmentSlice(nil) + result := CloneBrandVersionSlice(nil) assert.Nil(t, result) }) t.Run("empty", func(t *testing.T) { - given := []openrtb2.Segment{} - result := CloneSegmentSlice(given) + given := []openrtb2.BrandVersion{} + result := CloneBrandVersionSlice(given) assert.Empty(t, result) assert.NotSame(t, given, result) }) t.Run("one", func(t *testing.T) { - given := []openrtb2.Segment{ - {ID: "1", Ext: json.RawMessage(`{"anyField":1}`)}, + given := []openrtb2.BrandVersion{ + {Brand: "1", Version: []string{"s1", "s2"}, Ext: json.RawMessage(`{"anyField":1}`)}, } - result := CloneSegmentSlice(given) + result := CloneBrandVersionSlice(given) assert.Equal(t, given, result, "equality") assert.NotSame(t, given[0], result[0], "item-pointer") assert.NotSame(t, given[0].Ext, result[0].Ext, "item-pointer-ext") }) t.Run("many", func(t *testing.T) { - given := []openrtb2.Segment{ - {Ext: json.RawMessage(`{"anyField":1}`)}, - {Ext: json.RawMessage(`{"anyField":2}`)}, + given := []openrtb2.BrandVersion{ + {Brand: "1", Version: []string{"s1", "s2"}, Ext: json.RawMessage(`{"anyField":1}`)}, + {Brand: "2", Version: []string{"s3", "s4"}, Ext: json.RawMessage(`{"anyField":1}`)}, + {Brand: "3", Version: []string{"s5", "s6"}, Ext: json.RawMessage(`{"anyField":1}`)}, } - result := CloneSegmentSlice(given) + result := CloneBrandVersionSlice(given) assert.Equal(t, given, result, "equality") assert.NotSame(t, given[0], result[0], "item0-pointer") assert.NotSame(t, given[0].Ext, result[0].Ext, "item0-pointer-ext") assert.NotSame(t, given[1], result[1], "item1-pointer") assert.NotSame(t, given[1].Ext, result[1].Ext, "item1-pointer-ext") + assert.NotSame(t, given[2], result[2], "item1-pointer") + assert.NotSame(t, given[2].Ext, result[2].Ext, "item1-pointer-ext") }) } -func TestCloneSegment(t *testing.T) { - t.Run("populated", func(t *testing.T) { - given := openrtb2.Segment{ - ID: "anyID", - Name: "anyName", - Value: "anyValue", - Ext: json.RawMessage(`{"anyField":1}`), - } - result := CloneSegment(given) - assert.Equal(t, given, result, "equality") - assert.NotSame(t, given, result, "pointer") - assert.NotSame(t, given.Ext, result.Ext, "ext") - }) - - t.Run("assumptions", func(t *testing.T) { - assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Segment{})), - []string{ - "Ext", - }) - }) -} - -func TestCloneNetwork(t *testing.T) { +func TestCloneBrandVersion(t *testing.T) { t.Run("nil", func(t *testing.T) { - result := CloneNetwork(nil) + result := CloneBrandVersion(nil) assert.Nil(t, result) }) t.Run("empty", func(t *testing.T) { - given := &openrtb2.Network{} - result := CloneNetwork(given) + given := &openrtb2.BrandVersion{} + result := CloneBrandVersion(given) assert.Empty(t, result) assert.NotSame(t, given, result) }) t.Run("populated", func(t *testing.T) { - given := &openrtb2.Network{ - ID: "anyID", - Name: "anyName", - Domain: "anyDomain", - Ext: json.RawMessage(`{"anyField":1}`), + given := &openrtb2.BrandVersion{ + Brand: "Apple", + Version: []string{"s1"}, + Ext: json.RawMessage(`{"anyField":1}`), } - result := CloneNetwork(given) + result := CloneBrandVersion(given) assert.Equal(t, given, result, "equality") assert.NotSame(t, given, result, "pointer") assert.NotSame(t, given.Ext, result.Ext, "ext") }) t.Run("assumptions", func(t *testing.T) { - assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Network{})), + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.BrandVersion{})), []string{ + "Version", "Ext", }) }) } -func TestCloneChannel(t *testing.T) { +func TestCloneSource(t *testing.T) { t.Run("nil", func(t *testing.T) { - result := CloneChannel(nil) + result := CloneSource(nil) assert.Nil(t, result) }) t.Run("empty", func(t *testing.T) { - given := &openrtb2.Channel{} - result := CloneChannel(given) + given := &openrtb2.Source{} + result := CloneSource(given) assert.Empty(t, result) assert.NotSame(t, given, result) }) t.Run("populated", func(t *testing.T) { - given := &openrtb2.Channel{ - ID: "anyID", - Name: "anyName", - Domain: "anyDomain", - Ext: json.RawMessage(`{"anyField":1}`), + + given := &openrtb2.Source{ + FD: ptrutil.ToPtr[int8](1), + TID: "Tid", + PChain: "PChain", + SChain: &openrtb2.SupplyChain{ + Complete: 1, + Nodes: []openrtb2.SupplyChainNode{ + {ASI: "asi", Ext: json.RawMessage(`{"anyField":1}`)}, + }, + Ext: json.RawMessage(`{"anyField":2}`), + }, + Ext: json.RawMessage(`{"anyField":1}`), } - result := CloneChannel(given) + result := CloneSource(given) assert.Equal(t, given, result, "equality") assert.NotSame(t, given, result, "pointer") + assert.NotSame(t, given.FD, result.FD, "fd") + assert.NotSame(t, given.SChain, result.SChain, "schain") + assert.NotSame(t, given.SChain.Ext, result.SChain.Ext, "schain.ext") assert.NotSame(t, given.Ext, result.Ext, "ext") + assert.NotSame(t, given.SChain.Nodes[0].Ext, result.SChain.Nodes[0].Ext, "schain.nodes.ext") }) t.Run("assumptions", func(t *testing.T) { - assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Channel{})), + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Source{})), []string{ + "FD", + "SChain", "Ext", }) }) } -func TestCloneSite(t *testing.T) { +func TestCloneSChain(t *testing.T) { t.Run("nil", func(t *testing.T) { - result := CloneSite(nil) + result := CloneSource(nil) assert.Nil(t, result) }) t.Run("empty", func(t *testing.T) { - given := &openrtb2.Site{} - result := CloneSite(given) + given := &openrtb2.SupplyChain{} + result := CloneSChain(given) assert.Empty(t, result) assert.NotSame(t, given, result) }) t.Run("populated", func(t *testing.T) { - given := &openrtb2.Site{ - ID: "anyID", - Name: "anyName", - Domain: "anyDomain", - CatTax: adcom1.CatTaxIABContent10, - Cat: []string{"cat1"}, - SectionCat: []string{"sectionCat1"}, - PageCat: []string{"pageCat1"}, - Page: "anyPage", - Ref: "anyRef", - Search: "anySearch", - Mobile: 1, - PrivacyPolicy: 2, - Publisher: &openrtb2.Publisher{ID: "anyPublisher", Ext: json.RawMessage(`{"publisher":1}`)}, - Content: &openrtb2.Content{ID: "anyContent", Ext: json.RawMessage(`{"content":1}`)}, - Keywords: "anyKeywords", - KwArray: []string{"key1"}, - InventoryPartnerDomain: "anyInventoryPartnerDomain", - Ext: json.RawMessage(`{"anyField":1}`), + given := &openrtb2.SupplyChain{ + Complete: 1, + Nodes: []openrtb2.SupplyChainNode{ + {ASI: "asi", Ext: json.RawMessage(`{"anyField":1}`)}, + }, + Ext: json.RawMessage(`{"anyField":1}`), } - result := CloneSite(given) + result := CloneSChain(given) assert.Equal(t, given, result, "equality") assert.NotSame(t, given, result, "pointer") - assert.NotSame(t, given.Cat, result.Cat, "cat") - assert.NotSame(t, given.SectionCat, result.SectionCat, "sectioncat") - assert.NotSame(t, given.PageCat, result.PageCat, "pagecat") - assert.NotSame(t, given.Publisher, result.Publisher, "publisher") - assert.NotSame(t, given.Publisher.Ext, result.Publisher.Ext, "publisher-ext") - assert.NotSame(t, given.Content, result.Content, "content") - assert.NotSame(t, given.Content.Ext, result.Content.Ext, "content-ext") - assert.NotSame(t, given.KwArray, result.KwArray, "kwarray") + assert.NotSame(t, given.Nodes, result.Nodes, "nodes") + assert.NotSame(t, given.Nodes[0].Ext, result.Nodes[0].Ext, "nodes.ext") assert.NotSame(t, given.Ext, result.Ext, "ext") }) t.Run("assumptions", func(t *testing.T) { - assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Site{})), + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.SupplyChain{})), []string{ - "Cat", - "SectionCat", - "PageCat", - "Publisher", - "Content", - "KwArray", + "Nodes", "Ext", }) }) } -func TestCloneUser(t *testing.T) { +func TestCloneSupplyChainNodes(t *testing.T) { + var n int8 = 1 + np := &n t.Run("nil", func(t *testing.T) { - result := CloneUser(nil) + result := CloneSupplyChainNodes(nil) assert.Nil(t, result) }) t.Run("empty", func(t *testing.T) { - given := &openrtb2.User{} - result := CloneUser(given) + given := []openrtb2.SupplyChainNode{} + result := CloneSupplyChainNodes(given) assert.Empty(t, result) assert.NotSame(t, given, result) }) + t.Run("one", func(t *testing.T) { + given := []openrtb2.SupplyChainNode{ + {ASI: "asi", HP: np, Ext: json.RawMessage(`{"anyField":1}`)}, + } + result := CloneSupplyChainNodes(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given[0], result[0], "item-pointer") + assert.NotSame(t, given[0].HP, result[0].HP, "item-pointer-hp") + assert.NotSame(t, given[0].Ext, result[0].Ext, "item-pointer-ext") + }) + + t.Run("many", func(t *testing.T) { + given := []openrtb2.SupplyChainNode{ + {ASI: "asi", HP: np, Ext: json.RawMessage(`{"anyField":1}`)}, + {ASI: "asi", HP: np, Ext: json.RawMessage(`{"anyField":1}`)}, + {ASI: "asi", HP: np, Ext: json.RawMessage(`{"anyField":1}`)}, + } + result := CloneSupplyChainNodes(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given[0], result[0], "item0-pointer") + assert.NotSame(t, given[0].Ext, result[0].Ext, "item0-pointer-ext") + assert.NotSame(t, given[0].HP, result[0].HP, "item0-pointer-hp") + assert.NotSame(t, given[1], result[1], "item1-pointer") + assert.NotSame(t, given[1].Ext, result[1].Ext, "item1-pointer-ext") + assert.NotSame(t, given[1].HP, result[1].HP, "item1-pointer-hp") + assert.NotSame(t, given[2], result[2], "item2-pointer") + assert.NotSame(t, given[2].Ext, result[2].Ext, "item2-pointer-ext") + assert.NotSame(t, given[2].HP, result[2].HP, "item2-pointer-hp") + }) +} + +func TestCloneSupplyChainNode(t *testing.T) { t.Run("populated", func(t *testing.T) { - given := &openrtb2.User{ - ID: "anyID", - BuyerUID: "anyBuyerUID", - Yob: 1, - Gender: "anyGender", - Keywords: "anyKeywords", - KwArray: []string{"key1"}, - CustomData: "anyCustomData", - Geo: &openrtb2.Geo{Lat: 1.2, Lon: 2.3, Ext: json.RawMessage(`{"geo":1}`)}, - Data: []openrtb2.Data{{ID: "1", Ext: json.RawMessage(`{"data":1}`)}}, - Consent: "anyConsent", - EIDs: []openrtb2.EID{{Source: "1", Ext: json.RawMessage(`{"eid":1}`)}}, - Ext: json.RawMessage(`{"anyField":1}`), + var n int8 = 1 + np := &n + + given := openrtb2.SupplyChainNode{ + ASI: "asi", + HP: np, + Ext: json.RawMessage(`{"anyField":1}`), } - result := CloneUser(given) + result := CloneSupplyChainNode(given) assert.Equal(t, given, result, "equality") assert.NotSame(t, given, result, "pointer") - assert.NotSame(t, given.KwArray, result.KwArray, "cat") - assert.NotSame(t, given.Geo, result.Geo, "geo") - assert.NotSame(t, given.Geo.Ext, result.Geo.Ext, "geo-ext") - assert.NotSame(t, given.Data[0], result.Data[0], "data-item") - assert.NotSame(t, given.Data[0].Ext, result.Data[0].Ext, "data-item-ext") - assert.NotSame(t, given.EIDs[0], result.EIDs[0], "eids-item") - assert.NotSame(t, given.EIDs[0].Ext, result.EIDs[0].Ext, "eids-item-ext") assert.NotSame(t, given.Ext, result.Ext, "ext") + assert.NotSame(t, given.HP, result.HP, "hp") }) t.Run("assumptions", func(t *testing.T) { - assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.User{})), + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.SupplyChainNode{})), []string{ - "KwArray", - "Geo", - "Data", - "EIDs", + "HP", "Ext", }) }) @@ -551,8 +563,8 @@ func TestCloneGeo(t *testing.T) { t.Run("populated", func(t *testing.T) { given := &openrtb2.Geo{ - Lat: 1.234, - Lon: 5.678, + Lat: ptrutil.ToPtr(1.234), + Lon: ptrutil.ToPtr(5.678), Type: adcom1.LocationGPS, Accuracy: 1, LastFix: 2, @@ -569,12 +581,16 @@ func TestCloneGeo(t *testing.T) { result := CloneGeo(given) assert.Equal(t, given, result, "equality") assert.NotSame(t, given, result, "pointer") + assert.NotSame(t, given.Lat, result.Lat, "lat") + assert.NotSame(t, given.Lon, result.Lon, "lon") assert.NotSame(t, given.Ext, result.Ext, "ext") }) t.Run("assumptions", func(t *testing.T) { assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Geo{})), []string{ + "Lat", + "Lon", "Ext", }) }) @@ -700,53 +716,35 @@ func TestCloneUID(t *testing.T) { }) } -func TestCloneDOOH(t *testing.T) { +func TestCloneBidderReq(t *testing.T) { t.Run("nil", func(t *testing.T) { - result := CloneDOOH(nil) + result := CloneBidRequestPartial(nil) assert.Nil(t, result) }) t.Run("empty", func(t *testing.T) { - given := &openrtb2.DOOH{} - result := CloneDOOH(given) - assert.Empty(t, result) + given := &openrtb2.BidRequest{} + result := CloneBidRequestPartial(given) + assert.Equal(t, given, result) assert.NotSame(t, given, result) }) t.Run("populated", func(t *testing.T) { - given := &openrtb2.DOOH{ - ID: "anyID", - Name: "anyName", - VenueType: []string{"venue1"}, - VenueTypeTax: ptrutil.ToPtr(adcom1.VenueTaxonomyAdCom), - Publisher: &openrtb2.Publisher{ID: "anyPublisher", Ext: json.RawMessage(`{"publisher":1}`)}, - Domain: "anyDomain", - Keywords: "anyKeywords", - Content: &openrtb2.Content{ID: "anyContent", Ext: json.RawMessage(`{"content":1}`)}, - Ext: json.RawMessage(`{"anyField":1}`), + given := &openrtb2.BidRequest{ + ID: "anyID", + User: &openrtb2.User{ID: "testUserId"}, + Device: &openrtb2.Device{Carrier: "testCarrier"}, + Source: &openrtb2.Source{TID: "testTID"}, } - result := CloneDOOH(given) - assert.Equal(t, given, result, "equality") + result := CloneBidRequestPartial(given) + assert.Equal(t, given, result) assert.NotSame(t, given, result, "pointer") - assert.NotSame(t, given.VenueType, result.VenueType, "venuetype") - assert.NotSame(t, given.VenueTypeTax, result.VenueTypeTax, "venuetypetax") - assert.NotSame(t, given.Publisher, result.Publisher, "publisher") - assert.NotSame(t, given.Publisher.Ext, result.Publisher.Ext, "publisher-ext") - assert.NotSame(t, given.Content, result.Content, "content") - assert.NotSame(t, given.Content.Ext, result.Content.Ext, "content-ext") - assert.NotSame(t, given.Ext, result.Ext, "ext") + assert.NotSame(t, given.Device, result.Device, "device") + assert.NotSame(t, given.User, result.User, "user") + assert.NotSame(t, given.Source, result.Source, "source") }) - t.Run("assumptions", func(t *testing.T) { - assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.DOOH{})), - []string{ - "VenueType", - "VenueTypeTax", - "Publisher", - "Content", - "Ext", - }) - }) + // TODO: Implement a full bid request clone and track changes using an 'assumptions' test. } // discoverPointerFields returns the names of all fields of an object that are @@ -761,3 +759,44 @@ func discoverPointerFields(t reflect.Type) []string { } return fields } + +func TestCloneRegs(t *testing.T) { + t.Run("nil", func(t *testing.T) { + result := CloneRegs(nil) + assert.Nil(t, result) + }) + + t.Run("empty", func(t *testing.T) { + given := &openrtb2.Regs{} + result := CloneRegs(given) + assert.Empty(t, result) + assert.NotSame(t, given, result) + }) + + t.Run("populated", func(t *testing.T) { + given := &openrtb2.Regs{ + COPPA: 1, + GDPR: ptrutil.ToPtr(int8(0)), + USPrivacy: "1YNN", + GPP: "SomeGPPStrig", + GPPSID: []int8{1, 2, 3}, + Ext: json.RawMessage(`{"anyField":1}`), + } + result := CloneRegs(given) + assert.Equal(t, given, result, "equality") + assert.NotSame(t, given, result, "pointer") + assert.NotSame(t, given.GDPR, result.GDPR, "gdpr") + assert.NotSame(t, given.GPPSID, result.GPPSID, "gppsid[]") + assert.NotSame(t, given.GPPSID[0], result.GPPSID[0], "gppsid[0]") + assert.NotSame(t, given.Ext, result.Ext, "ext") + }) + + t.Run("assumptions", func(t *testing.T) { + assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Regs{})), + []string{ + "GDPR", + "GPPSID", + "Ext", + }) + }) +} diff --git a/ortb/default.go b/ortb/default.go index cd9d8c24759..ed2523b147a 100644 --- a/ortb/default.go +++ b/ortb/default.go @@ -1,8 +1,8 @@ package ortb import ( - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/ptrutil" ) const ( @@ -55,22 +55,24 @@ func setDefaultsTargeting(targeting *openrtb_ext.ExtRequestTargeting) bool { // Default price granularity can be overwritten for video, banner or native bid type // only in case targeting.MediaTypePriceGranularity.Video|Banner|Native != nil. - if targeting.MediaTypePriceGranularity.Video != nil { - if newVideoPG, updated := setDefaultsPriceGranularity(targeting.MediaTypePriceGranularity.Video); updated { - modified = true - targeting.MediaTypePriceGranularity.Video = newVideoPG + if targeting.MediaTypePriceGranularity != nil { + if targeting.MediaTypePriceGranularity.Video != nil { + if newVideoPG, updated := setDefaultsPriceGranularity(targeting.MediaTypePriceGranularity.Video); updated { + modified = true + targeting.MediaTypePriceGranularity.Video = newVideoPG + } } - } - if targeting.MediaTypePriceGranularity.Banner != nil { - if newBannerPG, updated := setDefaultsPriceGranularity(targeting.MediaTypePriceGranularity.Banner); updated { - modified = true - targeting.MediaTypePriceGranularity.Banner = newBannerPG + if targeting.MediaTypePriceGranularity.Banner != nil { + if newBannerPG, updated := setDefaultsPriceGranularity(targeting.MediaTypePriceGranularity.Banner); updated { + modified = true + targeting.MediaTypePriceGranularity.Banner = newBannerPG + } } - } - if targeting.MediaTypePriceGranularity.Native != nil { - if newNativePG, updated := setDefaultsPriceGranularity(targeting.MediaTypePriceGranularity.Native); updated { - modified = true - targeting.MediaTypePriceGranularity.Native = newNativePG + if targeting.MediaTypePriceGranularity.Native != nil { + if newNativePG, updated := setDefaultsPriceGranularity(targeting.MediaTypePriceGranularity.Native); updated { + modified = true + targeting.MediaTypePriceGranularity.Native = newNativePG + } } } diff --git a/ortb/default_test.go b/ortb/default_test.go index 8bda02ef4f5..fd77ba4c547 100644 --- a/ortb/default_test.go +++ b/ortb/default_test.go @@ -4,14 +4,14 @@ import ( "encoding/json" "testing" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/ptrutil" ) func TestSetDefaults(t *testing.T) { @@ -38,7 +38,7 @@ func TestSetDefaults(t *testing.T) { { name: "targeting", // tests integration with setDefaultsTargeting givenRequest: openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"targeting":{}}}`)}, - expectedRequest: openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true}}}`)}, + expectedRequest: openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"includewinners":true,"includebidderkeys":true}}}`)}, }, { name: "imp", // tests integration with setDefaultsImp @@ -162,7 +162,7 @@ func TestSetDefaultsTargeting(t *testing.T) { Precision: ptrutil.ToPtr(4), Ranges: nil, }, - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Video: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(4), Ranges: nil, @@ -179,7 +179,7 @@ func TestSetDefaultsTargeting(t *testing.T) { }, expectedTargeting: &openrtb_ext.ExtRequestTargeting{ PriceGranularity: &defaultGranularity, - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Video: &defaultGranularity, Banner: &defaultGranularity, Native: &defaultGranularity, @@ -189,6 +189,23 @@ func TestSetDefaultsTargeting(t *testing.T) { }, expectedModified: true, }, + { + name: "populated-ranges-nil-mediatypepricegranularity-nil", + givenTargeting: &openrtb_ext.ExtRequestTargeting{ + PriceGranularity: &openrtb_ext.PriceGranularity{ + Precision: ptrutil.ToPtr(4), + Ranges: nil, + }, + MediaTypePriceGranularity: nil, + }, + expectedTargeting: &openrtb_ext.ExtRequestTargeting{ + PriceGranularity: &defaultGranularity, + MediaTypePriceGranularity: nil, + IncludeWinners: ptrutil.ToPtr(DefaultTargetingIncludeWinners), + IncludeBidderKeys: ptrutil.ToPtr(DefaultTargetingIncludeBidderKeys), + }, + expectedModified: true, + }, { name: "populated-ranges-empty", givenTargeting: &openrtb_ext.ExtRequestTargeting{ @@ -211,7 +228,7 @@ func TestSetDefaultsTargeting(t *testing.T) { Precision: ptrutil.ToPtr(4), Ranges: []openrtb_ext.GranularityRange{}, }, - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Video: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(4), Ranges: []openrtb_ext.GranularityRange{}, @@ -228,7 +245,7 @@ func TestSetDefaultsTargeting(t *testing.T) { }, expectedTargeting: &openrtb_ext.ExtRequestTargeting{ PriceGranularity: &defaultGranularity, - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Video: &defaultGranularity, Banner: &defaultGranularity, Native: &defaultGranularity, @@ -265,7 +282,7 @@ func TestSetDefaultsTargeting(t *testing.T) { Precision: ptrutil.ToPtr(4), Ranges: []openrtb_ext.GranularityRange{{Min: 0, Max: 10, Increment: 1}}, }, - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Video: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(4), Ranges: []openrtb_ext.GranularityRange{{Min: 0, Max: 10, Increment: 1}}, @@ -287,7 +304,7 @@ func TestSetDefaultsTargeting(t *testing.T) { Precision: ptrutil.ToPtr(4), Ranges: []openrtb_ext.GranularityRange{{Min: 0, Max: 10, Increment: 1}}, }, - MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ + MediaTypePriceGranularity: &openrtb_ext.MediaTypePriceGranularity{ Video: &openrtb_ext.PriceGranularity{ Precision: ptrutil.ToPtr(4), Ranges: []openrtb_ext.GranularityRange{{Min: 0, Max: 10, Increment: 1}}}, diff --git a/ortb/request_validator.go b/ortb/request_validator.go new file mode 100644 index 00000000000..e959caa9345 --- /dev/null +++ b/ortb/request_validator.go @@ -0,0 +1,195 @@ +package ortb + +import ( + "encoding/json" + "fmt" + + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/stored_responses" +) + +type ValidationConfig struct { + SkipBidderParams bool + SkipNative bool +} + +type RequestValidator interface { + ValidateImp(imp *openrtb_ext.ImpWrapper, cfg ValidationConfig, index int, aliases map[string]string, hasStoredAuctionResponses bool, storedBidResponses stored_responses.ImpBidderStoredResp) []error +} + +func NewRequestValidator(bidderMap map[string]openrtb_ext.BidderName, disabledBidders map[string]string, paramsValidator openrtb_ext.BidderParamValidator) RequestValidator { + return &standardRequestValidator{ + bidderMap: bidderMap, + disabledBidders: disabledBidders, + paramsValidator: paramsValidator, + } +} + +type standardRequestValidator struct { + bidderMap map[string]openrtb_ext.BidderName + disabledBidders map[string]string + paramsValidator openrtb_ext.BidderParamValidator +} + +func (srv *standardRequestValidator) ValidateImp(imp *openrtb_ext.ImpWrapper, cfg ValidationConfig, index int, aliases map[string]string, hasStoredAuctionResponses bool, storedBidResponses stored_responses.ImpBidderStoredResp) []error { + if imp.ID == "" { + return []error{fmt.Errorf("request.imp[%d] missing required field: \"id\"", index)} + } + + if len(imp.Metric) != 0 { + return []error{fmt.Errorf("request.imp[%d].metric is not yet supported by prebid-server. Support may be added in the future", index)} + } + + if imp.Banner == nil && imp.Video == nil && imp.Audio == nil && imp.Native == nil { + return []error{fmt.Errorf("request.imp[%d] must contain at least one of \"banner\", \"video\", \"audio\", or \"native\"", index)} + } + + if err := validateBanner(imp.Banner, index, isInterstitial(imp)); err != nil { + return []error{err} + } + + if err := validateVideo(imp.Video, index); err != nil { + return []error{err} + } + + if err := validateAudio(imp.Audio, index); err != nil { + return []error{err} + } + + if !cfg.SkipNative { + if err := fillAndValidateNative(imp.Native, index); err != nil { + return []error{err} + } + } + + if err := validatePmp(imp.PMP, index); err != nil { + return []error{err} + } + + errL := srv.validateImpExt(imp, cfg, aliases, index, hasStoredAuctionResponses, storedBidResponses) + if len(errL) != 0 { + return errL + } + + return nil +} + +func (srv *standardRequestValidator) validateImpExt(imp *openrtb_ext.ImpWrapper, cfg ValidationConfig, aliases map[string]string, impIndex int, hasStoredAuctionResponses bool, storedBidResp stored_responses.ImpBidderStoredResp) []error { + if len(imp.Ext) == 0 { + return []error{fmt.Errorf("request.imp[%d].ext is required", impIndex)} + } + + impExt, err := imp.GetImpExt() + if err != nil { + return []error{err} + } + + prebid := impExt.GetOrCreatePrebid() + prebidModified := false + + bidderPromote := false + + if prebid.Bidder == nil { + prebid.Bidder = make(map[string]json.RawMessage) + bidderPromote = true + } + + ext := impExt.GetExt() + extModified := false + + // promote imp[].ext.BIDDER to newer imp[].ext.prebid.bidder.BIDDER location, with the later taking precedence + if bidderPromote { + for k, v := range ext { + if openrtb_ext.IsPotentialBidder(k) { + if _, exists := prebid.Bidder[k]; !exists { + prebid.Bidder[k] = v + prebidModified = true + } + delete(ext, k) + extModified = true + } + } + } + + if hasStoredAuctionResponses && prebid.StoredAuctionResponse == nil { + return []error{fmt.Errorf("request validation failed. The StoredAuctionResponse.ID field must be completely present with, or completely absent from, all impressions in request. No StoredAuctionResponse data found for request.imp[%d].ext.prebid \n", impIndex)} + } + + if err := srv.validateStoredBidResponses(prebid, storedBidResp, imp.ID); err != nil { + return []error{err} + } + + errL := []error{} + + for bidder, val := range prebid.Bidder { + coreBidder, _ := openrtb_ext.NormalizeBidderName(bidder) + if tmp, isAlias := aliases[bidder]; isAlias { + coreBidder = openrtb_ext.BidderName(tmp) + } + + if coreBidderNormalized, isValid := srv.bidderMap[coreBidder.String()]; isValid { + if !cfg.SkipBidderParams { + if err := srv.paramsValidator.Validate(coreBidderNormalized, val); err != nil { + return []error{fmt.Errorf("request.imp[%d].ext.prebid.bidder.%s failed validation.\n%v", impIndex, bidder, err)} + } + } + } else { + if msg, isDisabled := srv.disabledBidders[bidder]; isDisabled { + errL = append(errL, &errortypes.BidderTemporarilyDisabled{Message: msg}) + delete(prebid.Bidder, bidder) + prebidModified = true + } else if bidderPromote { + errL = append(errL, &errortypes.Warning{Message: fmt.Sprintf("request.imp[%d].ext contains unknown bidder: '%s', ignoring", impIndex, bidder)}) + ext[bidder] = val + delete(prebid.Bidder, bidder) + prebidModified = true + } else { + return []error{fmt.Errorf("request.imp[%d].ext.prebid.bidder contains unknown bidder: %s. Did you forget an alias in request.ext.prebid.aliases?", impIndex, bidder)} + } + } + } + + if len(prebid.Bidder) == 0 { + errL = append(errL, fmt.Errorf("request.imp[%d].ext.prebid.bidder must contain at least one bidder", impIndex)) + return errL + } + + if prebidModified { + impExt.SetPrebid(prebid) + } + if extModified { + impExt.SetExt(ext) + } + + return errL +} + +func (srv *standardRequestValidator) validateStoredBidResponses(prebid *openrtb_ext.ExtImpPrebid, storedBidResp stored_responses.ImpBidderStoredResp, impId string) error { + if storedBidResp == nil && len(prebid.StoredBidResponse) == 0 { + return nil + } + + if storedBidResp == nil { + return generateStoredBidResponseValidationError(impId) + } + if bidResponses, ok := storedBidResp[impId]; ok { + if len(bidResponses) != len(prebid.Bidder) { + return generateStoredBidResponseValidationError(impId) + } + + for bidderName := range bidResponses { + if _, bidderNameOk := openrtb_ext.NormalizeBidderName(bidderName); !bidderNameOk { + return fmt.Errorf(`unrecognized bidder "%v"`, bidderName) + } + if _, present := prebid.Bidder[bidderName]; !present { + return generateStoredBidResponseValidationError(impId) + } + } + } + return nil +} + +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) +} diff --git a/ortb/request_validator_audio.go b/ortb/request_validator_audio.go new file mode 100644 index 00000000000..f1ed46f5650 --- /dev/null +++ b/ortb/request_validator_audio.go @@ -0,0 +1,34 @@ +package ortb + +import ( + "fmt" + + "github.com/prebid/openrtb/v20/openrtb2" +) + +func validateAudio(audio *openrtb2.Audio, impIndex int) error { + if audio == nil { + return nil + } + + if len(audio.MIMEs) < 1 { + return fmt.Errorf("request.imp[%d].audio.mimes must contain at least one supported MIME type", impIndex) + } + + // The following fields were previously uints in the OpenRTB library we use, but have + // since been changed to ints. We decided to maintain the non-negative check. + if audio.Sequence < 0 { + return fmt.Errorf("request.imp[%d].audio.sequence must be a positive number", impIndex) + } + if audio.MaxSeq < 0 { + return fmt.Errorf("request.imp[%d].audio.maxseq must be a positive number", impIndex) + } + if audio.MinBitrate < 0 { + return fmt.Errorf("request.imp[%d].audio.minbitrate must be a positive number", impIndex) + } + if audio.MaxBitrate < 0 { + return fmt.Errorf("request.imp[%d].audio.maxbitrate must be a positive number", impIndex) + } + + return nil +} diff --git a/ortb/request_validator_audio_test.go b/ortb/request_validator_audio_test.go new file mode 100644 index 00000000000..9b54f925546 --- /dev/null +++ b/ortb/request_validator_audio_test.go @@ -0,0 +1,99 @@ +package ortb + +import ( + "testing" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/stretchr/testify/assert" +) + +func TestValidateAudio(t *testing.T) { + tests := []struct { + name string + audio *openrtb2.Audio + wantError bool + }{ + { + name: "nil", + audio: nil, + wantError: false, + }, + { + name: "well_formed", + audio: &openrtb2.Audio{ + MIMEs: []string{"MIME1"}, + Sequence: 0, + MaxSeq: 0, + MinBitrate: 0, + MaxBitrate: 0, + }, + wantError: false, + }, + { + name: "mimes_is_zero", + audio: &openrtb2.Audio{ + MIMEs: []string{}, + Sequence: 0, + MaxSeq: 0, + MinBitrate: 0, + MaxBitrate: 0, + }, + wantError: true, + }, + { + name: "negative_sequence", + audio: &openrtb2.Audio{ + MIMEs: []string{"MIME1"}, + Sequence: -1, + MaxSeq: 0, + MinBitrate: 0, + MaxBitrate: 0, + }, + wantError: true, + }, + { + name: "negative_max_sequence", + audio: &openrtb2.Audio{ + MIMEs: []string{"MIME1"}, + Sequence: 0, + MaxSeq: -1, + MinBitrate: 0, + MaxBitrate: 0, + }, + wantError: true, + }, + { + name: "negative_min_bit_rate", + audio: &openrtb2.Audio{ + MIMEs: []string{"MIME1"}, + Sequence: 0, + MaxSeq: 0, + MinBitrate: -1, + MaxBitrate: 0, + }, + wantError: true, + }, + { + name: "negative_max_bit_rate", + audio: &openrtb2.Audio{ + MIMEs: []string{"MIME1"}, + Sequence: 0, + MaxSeq: 0, + MinBitrate: 0, + MaxBitrate: -1, + }, + wantError: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := validateAudio(test.audio, 1) + if test.wantError { + assert.Error(t, result) + } else { + assert.NoError(t, result) + } + }) + } +} diff --git a/ortb/request_validator_banner.go b/ortb/request_validator_banner.go new file mode 100644 index 00000000000..fea68121a9a --- /dev/null +++ b/ortb/request_validator_banner.go @@ -0,0 +1,95 @@ +package ortb + +import ( + "fmt" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +func isInterstitial(imp *openrtb_ext.ImpWrapper) bool { + return imp.Instl == 1 +} + +func validateBanner(banner *openrtb2.Banner, impIndex int, isInterstitial bool) error { + if banner == nil { + return nil + } + + // The following fields were previously uints in the OpenRTB library we use, but have + // since been changed to ints. We decided to maintain the non-negative check. + if banner.W != nil && *banner.W < 0 { + return fmt.Errorf("request.imp[%d].banner.w must be a positive number", impIndex) + } + if banner.H != nil && *banner.H < 0 { + return fmt.Errorf("request.imp[%d].banner.h must be a positive number", impIndex) + } + + // The following fields are deprecated in the OpenRTB 2.5 spec but are still present + // in the OpenRTB library we use. Enforce they are not specified. + if banner.WMin != 0 { + return fmt.Errorf("request.imp[%d].banner uses unsupported property: \"wmin\". Use the \"format\" array instead.", impIndex) + } + if banner.WMax != 0 { + return fmt.Errorf("request.imp[%d].banner uses unsupported property: \"wmax\". Use the \"format\" array instead.", impIndex) + } + if banner.HMin != 0 { + return fmt.Errorf("request.imp[%d].banner uses unsupported property: \"hmin\". Use the \"format\" array instead.", impIndex) + } + if banner.HMax != 0 { + return fmt.Errorf("request.imp[%d].banner uses unsupported property: \"hmax\". Use the \"format\" array instead.", impIndex) + } + + hasRootSize := banner.H != nil && banner.W != nil && *banner.H > 0 && *banner.W > 0 + if !hasRootSize && len(banner.Format) == 0 && !isInterstitial { + return fmt.Errorf("request.imp[%d].banner has no sizes. Define \"w\" and \"h\", or include \"format\" elements.", impIndex) + } + + for i, format := range banner.Format { + if err := validateFormat(&format, impIndex, i); err != nil { + return err + } + } + + return nil +} + +func validateFormat(format *openrtb2.Format, impIndex, formatIndex int) error { + if format == nil { + return nil + } + usesHW := format.W != 0 || format.H != 0 + usesRatios := format.WMin != 0 || format.WRatio != 0 || format.HRatio != 0 + + // The following fields were previously uints in the OpenRTB library we use, but have + // since been changed to ints. We decided to maintain the non-negative check. + if format.W < 0 { + return fmt.Errorf("request.imp[%d].banner.format[%d].w must be a positive number", impIndex, formatIndex) + } + if format.H < 0 { + return fmt.Errorf("request.imp[%d].banner.format[%d].h must be a positive number", impIndex, formatIndex) + } + if format.WRatio < 0 { + return fmt.Errorf("request.imp[%d].banner.format[%d].wratio must be a positive number", impIndex, formatIndex) + } + if format.HRatio < 0 { + return fmt.Errorf("request.imp[%d].banner.format[%d].hratio must be a positive number", impIndex, formatIndex) + } + if format.WMin < 0 { + return fmt.Errorf("request.imp[%d].banner.format[%d].wmin must be a positive number", impIndex, formatIndex) + } + + if usesHW && usesRatios { + return fmt.Errorf("Request imp[%d].banner.format[%d] should define *either* {w, h} *or* {wmin, wratio, hratio}, but not both. If both are valid, send two \"format\" objects in the request.", impIndex, formatIndex) + } + if !usesHW && !usesRatios { + return fmt.Errorf("Request imp[%d].banner.format[%d] should define *either* {w, h} (for static size requirements) *or* {wmin, wratio, hratio} (for flexible sizes) to be non-zero.", impIndex, formatIndex) + } + if usesHW && (format.W == 0 || format.H == 0) { + return fmt.Errorf("Request imp[%d].banner.format[%d] must define non-zero \"h\" and \"w\" properties.", impIndex, formatIndex) + } + if usesRatios && (format.WMin == 0 || format.WRatio == 0 || format.HRatio == 0) { + return fmt.Errorf("Request imp[%d].banner.format[%d] must define non-zero \"wmin\", \"wratio\", and \"hratio\" properties.", impIndex, formatIndex) + } + return nil +} diff --git a/ortb/request_validator_banner_test.go b/ortb/request_validator_banner_test.go new file mode 100644 index 00000000000..e131c32e98e --- /dev/null +++ b/ortb/request_validator_banner_test.go @@ -0,0 +1,288 @@ +package ortb + +import ( + "testing" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +func TestValidateBanner(t *testing.T) { + tests := []struct { + name string + banner *openrtb2.Banner + interstitial bool + wantError bool + }{ + { + name: "nil", + banner: nil, + interstitial: false, + wantError: false, + }, + { + name: "no_root_or_format_and_interstitial", + banner: &openrtb2.Banner{}, + interstitial: true, + wantError: false, + }, + { + name: "no_root_or_format_and_not_interstitial", + banner: &openrtb2.Banner{}, + interstitial: false, + wantError: true, + }, + { + name: "well_formed_root", + banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](11), + H: ptrutil.ToPtr[int64](1), + }, + interstitial: false, + wantError: false, + }, + { + name: "well_formed_format", + banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 1, + H: 1, + }, + }, + }, + interstitial: false, + wantError: false, + }, + { + name: "invalid_format", + banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + { + W: 1, + H: 1, + HRatio: -1, + }, + }, + }, + interstitial: false, + wantError: true, + }, + { + name: "negative_width", + banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](-1), + H: ptrutil.ToPtr[int64](1), + }, + interstitial: false, + wantError: true, + }, + { + name: "negative_height", + banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](1), + H: ptrutil.ToPtr[int64](-1), + }, + interstitial: false, + wantError: true, + }, + { + name: "nonzero_wmin", + banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](1), + H: ptrutil.ToPtr[int64](1), + WMin: 1, + }, + interstitial: false, + wantError: true, + }, + { + name: "nonzero_wmax", + banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](1), + H: ptrutil.ToPtr[int64](1), + WMax: 1, + }, + interstitial: false, + wantError: true, + }, + { + name: "nonzero_hmin", + banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](1), + H: ptrutil.ToPtr[int64](1), + HMin: 1, + }, + interstitial: false, + wantError: true, + }, + { + name: "nonzero_hmax", + banner: &openrtb2.Banner{ + W: ptrutil.ToPtr[int64](1), + H: ptrutil.ToPtr[int64](1), + HMax: 1, + }, + interstitial: false, + wantError: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := validateBanner(test.banner, 1, test.interstitial) + if test.wantError { + assert.Error(t, result) + } else { + assert.NoError(t, result) + } + }) + } +} + +func TestValidateFormat(t *testing.T) { + tests := []struct { + name string + format *openrtb2.Format + wantError bool + }{ + { + name: "nil", + format: nil, + wantError: false, + }, + { + name: "well_formed", + format: &openrtb2.Format{ + W: 1, + H: 1, + WRatio: 0, + HRatio: 0, + WMin: 0, + }, + wantError: false, + }, + { + name: "well_formed_using_ratios", + format: &openrtb2.Format{ + W: 0, + H: 0, + WRatio: 1, + HRatio: 1, + WMin: 1, + }, + wantError: false, + }, + { + name: "negative_width", + format: &openrtb2.Format{ + W: -1, + H: 1, + WRatio: 0, + HRatio: 0, + WMin: 0, + }, + wantError: true, + }, + { + name: "negative_height", + format: &openrtb2.Format{ + W: 1, + H: -1, + WRatio: 0, + HRatio: 0, + WMin: 0, + }, + wantError: true, + }, + { + name: "negative_width_ratio", + format: &openrtb2.Format{ + W: 0, + H: 0, + WRatio: -1, + HRatio: 1, + WMin: 1, + }, + wantError: true, + }, + { + name: "negative_height_ratio", + format: &openrtb2.Format{ + W: 0, + H: 0, + WRatio: 1, + HRatio: -1, + WMin: 1, + }, + wantError: true, + }, + { + name: "negative_height_wmin", + format: &openrtb2.Format{ + W: 1, + H: 1, + WRatio: 1, + HRatio: 1, + WMin: -1, + }, + wantError: true, + }, + { + name: "using_both_formats", + format: &openrtb2.Format{ + W: 1, + H: 1, + WRatio: 1, + HRatio: 1, + WMin: 1, + }, + wantError: true, + }, + { + name: "using_neither_format", + format: &openrtb2.Format{ + W: 0, + H: 0, + WRatio: 0, + HRatio: 0, + WMin: 0, + }, + wantError: true, + }, + { + name: "using_non_ratios_but_zeros", + format: &openrtb2.Format{ + W: 1, + H: 0, + WRatio: 0, + HRatio: 0, + WMin: 0, + }, + wantError: true, + }, + { + name: "using_ratios_but_zeros", + format: &openrtb2.Format{ + W: 0, + H: 0, + WRatio: 1, + HRatio: 0, + WMin: 0, + }, + wantError: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := validateFormat(test.format, 1, 1) + if test.wantError { + assert.Error(t, result) + } else { + assert.NoError(t, result) + } + }) + } +} diff --git a/ortb/request_validator_native.go b/ortb/request_validator_native.go new file mode 100644 index 00000000000..1209a13913f --- /dev/null +++ b/ortb/request_validator_native.go @@ -0,0 +1,273 @@ +package ortb + +import ( + "encoding/json" + "fmt" + + "github.com/prebid/openrtb/v20/adcom1" + "github.com/prebid/openrtb/v20/native1" + nativeRequests "github.com/prebid/openrtb/v20/native1/request" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +// fillAndValidateNative validates the request, and assigns the Asset IDs as recommended by the Native v1.2 spec. +func fillAndValidateNative(n *openrtb2.Native, impIndex int) error { + if n == nil { + return nil + } + + if len(n.Request) == 0 { + return fmt.Errorf("request.imp[%d].native missing required property \"request\"", impIndex) + } + var nativePayload nativeRequests.Request + if err := jsonutil.UnmarshalValid(json.RawMessage(n.Request), &nativePayload); err != nil { + return err + } + + if err := validateNativeContextTypes(nativePayload.Context, nativePayload.ContextSubType, impIndex); err != nil { + return err + } + if err := validateNativePlacementType(nativePayload.PlcmtType, impIndex); err != nil { + return err + } + if err := fillAndValidateNativeAssets(nativePayload.Assets, impIndex); err != nil { + return err + } + if err := validateNativeEventTrackers(nativePayload.EventTrackers, impIndex); err != nil { + return err + } + + serialized, err := jsonutil.Marshal(nativePayload) + if err != nil { + return err + } + n.Request = string(serialized) + return nil +} + +func validateNativeContextTypes(cType native1.ContextType, cSubtype native1.ContextSubType, impIndex int) error { + if cType == 0 { + // Context is only recommended, so none is a valid type. + return nil + } + if cType < native1.ContextTypeContent || (cType > native1.ContextTypeProduct && cType < openrtb_ext.NativeExchangeSpecificLowerBound) { + return fmt.Errorf("request.imp[%d].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex) + } + if cSubtype < 0 { + return fmt.Errorf("request.imp[%d].native.request.contextsubtype value can't be less than 0. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex) + } + if cSubtype == 0 { + return nil + } + if cSubtype >= native1.ContextSubTypeGeneral && cSubtype <= native1.ContextSubTypeUserGenerated { + if cType != native1.ContextTypeContent && cType < openrtb_ext.NativeExchangeSpecificLowerBound { + return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype) + } + return nil + } + if cSubtype >= native1.ContextSubTypeSocial && cSubtype <= native1.ContextSubTypeChat { + if cType != native1.ContextTypeSocial && cType < openrtb_ext.NativeExchangeSpecificLowerBound { + return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype) + } + return nil + } + if cSubtype >= native1.ContextSubTypeSelling && cSubtype <= native1.ContextSubTypeProductReview { + if cType != native1.ContextTypeProduct && cType < openrtb_ext.NativeExchangeSpecificLowerBound { + return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype) + } + return nil + } + if cSubtype >= openrtb_ext.NativeExchangeSpecificLowerBound { + return nil + } + + return fmt.Errorf("request.imp[%d].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex) +} + +func validateNativePlacementType(pt native1.PlacementType, impIndex int) error { + if pt == 0 { + // Placement Type is only recommended, not required. + return nil + } + if pt < native1.PlacementTypeFeed || (pt > native1.PlacementTypeRecommendationWidget && pt < openrtb_ext.NativeExchangeSpecificLowerBound) { + return fmt.Errorf("request.imp[%d].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", impIndex) + } + return nil +} + +func fillAndValidateNativeAssets(assets []nativeRequests.Asset, impIndex int) error { + if len(assets) < 1 { + return fmt.Errorf("request.imp[%d].native.request.assets must be an array containing at least one object", impIndex) + } + + assetIDs := make(map[int64]struct{}, len(assets)) + + // If none of the asset IDs are defined by the caller, then prebid server should assign its own unique IDs. But + // if the caller did assign its own asset IDs, then prebid server will respect those IDs + assignAssetIDs := true + for i := 0; i < len(assets); i++ { + assignAssetIDs = assignAssetIDs && (assets[i].ID == 0) + } + + for i := 0; i < len(assets); i++ { + if err := validateNativeAsset(assets[i], impIndex, i); err != nil { + return err + } + + if assignAssetIDs { + assets[i].ID = int64(i) + continue + } + + // Each asset should have a unique ID thats assigned by the caller + if _, ok := assetIDs[assets[i].ID]; ok { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].id is already being used by another asset. Each asset ID must be unique.", impIndex, i) + } + + assetIDs[assets[i].ID] = struct{}{} + } + + return nil +} + +func validateNativeAsset(asset nativeRequests.Asset, impIndex int, assetIndex int) error { + assetErr := "request.imp[%d].native.request.assets[%d] must define exactly one of {title, img, video, data}" + foundType := false + + if asset.Title != nil { + foundType = true + if err := validateNativeAssetTitle(asset.Title, impIndex, assetIndex); err != nil { + return err + } + } + + if asset.Img != nil { + if foundType { + return fmt.Errorf(assetErr, impIndex, assetIndex) + } + foundType = true + if err := validateNativeAssetImage(asset.Img, impIndex, assetIndex); err != nil { + return err + } + } + + if asset.Video != nil { + if foundType { + return fmt.Errorf(assetErr, impIndex, assetIndex) + } + foundType = true + if err := validateNativeAssetVideo(asset.Video, impIndex, assetIndex); err != nil { + return err + } + } + + if asset.Data != nil { + if foundType { + return fmt.Errorf(assetErr, impIndex, assetIndex) + } + foundType = true + if err := validateNativeAssetData(asset.Data, impIndex, assetIndex); err != nil { + return err + } + } + + if !foundType { + return fmt.Errorf(assetErr, impIndex, assetIndex) + } + + return nil +} + +func validateNativeEventTrackers(trackers []nativeRequests.EventTracker, impIndex int) error { + for i := 0; i < len(trackers); i++ { + if err := validateNativeEventTracker(trackers[i], impIndex, i); err != nil { + return err + } + } + return nil +} + +func validateNativeAssetTitle(title *nativeRequests.Title, impIndex int, assetIndex int) error { + if title.Len < 1 { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].title.len must be a positive number", impIndex, assetIndex) + } + return nil +} + +func validateNativeEventTracker(tracker nativeRequests.EventTracker, impIndex int, eventIndex int) error { + if tracker.Event < native1.EventTypeImpression || (tracker.Event > native1.EventTypeViewableVideo50 && tracker.Event < openrtb_ext.NativeExchangeSpecificLowerBound) { + return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex) + } + if len(tracker.Methods) < 1 { + return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].method is required. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex) + } + for methodIndex, method := range tracker.Methods { + if method < native1.EventTrackingMethodImage || (method > native1.EventTrackingMethodJS && method < openrtb_ext.NativeExchangeSpecificLowerBound) { + return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].methods[%d] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex, methodIndex) + } + } + + return nil +} + +func validateNativeAssetImage(img *nativeRequests.Image, impIndex int, assetIndex int) error { + if img.W < 0 { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.w must be a positive integer", impIndex, assetIndex) + } + if img.H < 0 { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.h must be a positive integer", impIndex, assetIndex) + } + if img.WMin < 0 { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.wmin must be a positive integer", impIndex, assetIndex) + } + if img.HMin < 0 { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.hmin must be a positive integer", impIndex, assetIndex) + } + return nil +} + +func validateNativeAssetVideo(video *nativeRequests.Video, impIndex int, assetIndex int) error { + if len(video.MIMEs) < 1 { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.mimes must be an array with at least one MIME type", impIndex, assetIndex) + } + if video.MinDuration < 1 { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.minduration must be a positive integer", impIndex, assetIndex) + } + if video.MaxDuration < 1 { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.maxduration must be a positive integer", impIndex, assetIndex) + } + if err := validateNativeVideoProtocols(video.Protocols, impIndex, assetIndex); err != nil { + return err + } + + return nil +} + +func validateNativeAssetData(data *nativeRequests.Data, impIndex int, assetIndex int) error { + if data.Type < native1.DataAssetTypeSponsored || (data.Type > native1.DataAssetTypeCTAText && data.Type < 500) { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", impIndex, assetIndex) + } + + return nil +} + +func validateNativeVideoProtocols(protocols []adcom1.MediaCreativeSubtype, impIndex int, assetIndex int) error { + if len(protocols) < 1 { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.protocols must be an array with at least one element", impIndex, assetIndex) + } + for i := 0; i < len(protocols); i++ { + if err := validateNativeVideoProtocol(protocols[i], impIndex, assetIndex, i); err != nil { + return err + } + } + return nil +} + +func validateNativeVideoProtocol(protocol adcom1.MediaCreativeSubtype, impIndex int, assetIndex int, protocolIndex int) error { + if protocol < adcom1.CreativeVAST10 || protocol > adcom1.CreativeDAAST10Wrapper { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.protocols[%d] is invalid. See Section 5.8: https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf#page=52", impIndex, assetIndex, protocolIndex) + } + return nil +} diff --git a/ortb/request_validator_native_test.go b/ortb/request_validator_native_test.go new file mode 100644 index 00000000000..f44cfef342a --- /dev/null +++ b/ortb/request_validator_native_test.go @@ -0,0 +1,428 @@ +package ortb + +import ( + "testing" + + "github.com/prebid/openrtb/v20/native1" + nativeRequests "github.com/prebid/openrtb/v20/native1/request" + "github.com/stretchr/testify/assert" +) + +func TestValidateNativeContextTypes(t *testing.T) { + impIndex := 4 + + testCases := []struct { + description string + givenContextType native1.ContextType + givenSubType native1.ContextSubType + expectedError string + }{ + { + description: "No Types Specified", + givenContextType: 0, + givenSubType: 0, + expectedError: "", + }, + { + description: "All Types Exchange Specific", + givenContextType: 500, + givenSubType: 500, + expectedError: "", + }, + { + description: "Context Type Known Value - Sub Type Unspecified", + givenContextType: 1, + givenSubType: 0, + expectedError: "", + }, + { + description: "Context Type Negative", + givenContextType: -1, + givenSubType: 0, + expectedError: "request.imp[4].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Context Type Just Above Range", + givenContextType: 4, // Range is currently 1-3 + givenSubType: 0, + expectedError: "request.imp[4].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Sub Type Negative", + givenContextType: 1, + givenSubType: -1, + expectedError: "request.imp[4].native.request.contextsubtype value can't be less than 0. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Content - Sub Type Just Below Range", + givenContextType: 1, // Content constant + givenSubType: 9, // Content range is currently 10-15 + expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Content - Sub Type In Range", + givenContextType: 1, // Content constant + givenSubType: 10, // Content range is currently 10-15 + expectedError: "", + }, + { + description: "Content - Sub Type In Range - Context Type Exchange Specific Boundary", + givenContextType: 500, + givenSubType: 10, // Content range is currently 10-15 + expectedError: "", + }, + { + description: "Content - Sub Type In Range - Context Type Exchange Specific Boundary + 1", + givenContextType: 501, + givenSubType: 10, // Content range is currently 10-15 + expectedError: "", + }, + { + description: "Content - Sub Type Just Above Range", + givenContextType: 1, // Content constant + givenSubType: 16, // Content range is currently 10-15 + expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Content - Sub Type Exchange Specific Boundary", + givenContextType: 1, // Content constant + givenSubType: 500, + expectedError: "", + }, + { + description: "Content - Sub Type Exchange Specific Boundary + 1", + givenContextType: 1, // Content constant + givenSubType: 501, + expectedError: "", + }, + { + description: "Content - Invalid Context Type", + givenContextType: 2, // Not content constant + givenSubType: 10, // Content range is currently 10-15 + expectedError: "request.imp[4].native.request.context is 2, but contextsubtype is 10. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Social - Sub Type Just Below Range", + givenContextType: 2, // Social constant + givenSubType: 19, // Social range is currently 20-22 + expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Social - Sub Type In Range", + givenContextType: 2, // Social constant + givenSubType: 20, // Social range is currently 20-22 + expectedError: "", + }, + { + description: "Social - Sub Type In Range - Context Type Exchange Specific Boundary", + givenContextType: 500, + givenSubType: 20, // Social range is currently 20-22 + expectedError: "", + }, + { + description: "Social - Sub Type In Range - Context Type Exchange Specific Boundary + 1", + givenContextType: 501, + givenSubType: 20, // Social range is currently 20-22 + expectedError: "", + }, + { + description: "Social - Sub Type Just Above Range", + givenContextType: 2, // Social constant + givenSubType: 23, // Social range is currently 20-22 + expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Social - Sub Type Exchange Specific Boundary", + givenContextType: 2, // Social constant + givenSubType: 500, + expectedError: "", + }, + { + description: "Social - Sub Type Exchange Specific Boundary + 1", + givenContextType: 2, // Social constant + givenSubType: 501, + expectedError: "", + }, + { + description: "Social - Invalid Context Type", + givenContextType: 3, // Not social constant + givenSubType: 20, // Social range is currently 20-22 + expectedError: "request.imp[4].native.request.context is 3, but contextsubtype is 20. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Product - Sub Type Just Below Range", + givenContextType: 3, // Product constant + givenSubType: 29, // Product range is currently 30-32 + expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Product - Sub Type In Range", + givenContextType: 3, // Product constant + givenSubType: 30, // Product range is currently 30-32 + expectedError: "", + }, + { + description: "Product - Sub Type In Range - Context Type Exchange Specific Boundary", + givenContextType: 500, + givenSubType: 30, // Product range is currently 30-32 + expectedError: "", + }, + { + description: "Product - Sub Type In Range - Context Type Exchange Specific Boundary + 1", + givenContextType: 501, + givenSubType: 30, // Product range is currently 30-32 + expectedError: "", + }, + { + description: "Product - Sub Type Just Above Range", + givenContextType: 3, // Product constant + givenSubType: 33, // Product range is currently 30-32 + expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Product - Sub Type Exchange Specific Boundary", + givenContextType: 3, // Product constant + givenSubType: 500, + expectedError: "", + }, + { + description: "Product - Sub Type Exchange Specific Boundary + 1", + givenContextType: 3, // Product constant + givenSubType: 501, + expectedError: "", + }, + { + description: "Product - Invalid Context Type", + givenContextType: 1, // Not product constant + givenSubType: 30, // Product range is currently 30-32 + expectedError: "request.imp[4].native.request.context is 1, but contextsubtype is 30. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + } + + for _, test := range testCases { + err := validateNativeContextTypes(test.givenContextType, test.givenSubType, impIndex) + if test.expectedError == "" { + assert.NoError(t, err, test.description) + } else { + assert.EqualError(t, err, test.expectedError, test.description) + } + } +} + +func TestValidateNativePlacementType(t *testing.T) { + impIndex := 4 + + testCases := []struct { + description string + givenPlacementType native1.PlacementType + expectedError string + }{ + { + description: "Not Specified", + givenPlacementType: 0, + expectedError: "", + }, + { + description: "Known Value", + givenPlacementType: 1, // Range is currently 1-4 + expectedError: "", + }, + { + description: "Exchange Specific - Boundary", + givenPlacementType: 500, + expectedError: "", + }, + { + description: "Exchange Specific - Boundary + 1", + givenPlacementType: 501, + expectedError: "", + }, + { + description: "Negative", + givenPlacementType: -1, + expectedError: "request.imp[4].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", + }, + { + description: "Just Above Range", + givenPlacementType: 5, // Range is currently 1-4 + expectedError: "request.imp[4].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", + }, + } + + for _, test := range testCases { + err := validateNativePlacementType(test.givenPlacementType, impIndex) + if test.expectedError == "" { + assert.NoError(t, err, test.description) + } else { + assert.EqualError(t, err, test.expectedError, test.description) + } + } +} + +func TestValidateNativeEventTracker(t *testing.T) { + impIndex := 4 + eventIndex := 8 + + testCases := []struct { + description string + givenEvent nativeRequests.EventTracker + expectedError string + }{ + { + description: "Valid", + givenEvent: nativeRequests.EventTracker{ + Event: 1, + Methods: []native1.EventTrackingMethod{1}, + }, + expectedError: "", + }, + { + description: "Event - Exchange Specific - Boundary", + givenEvent: nativeRequests.EventTracker{ + Event: 500, + Methods: []native1.EventTrackingMethod{1}, + }, + expectedError: "", + }, + { + description: "Event - Exchange Specific - Boundary + 1", + givenEvent: nativeRequests.EventTracker{ + Event: 501, + Methods: []native1.EventTrackingMethod{1}, + }, + expectedError: "", + }, + { + description: "Event - Negative", + givenEvent: nativeRequests.EventTracker{ + Event: -1, + Methods: []native1.EventTrackingMethod{1}, + }, + expectedError: "request.imp[4].native.request.eventtrackers[8].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", + }, + { + description: "Event - Just Above Range", + givenEvent: nativeRequests.EventTracker{ + Event: 5, // Range is currently 1-4 + Methods: []native1.EventTrackingMethod{1}, + }, + expectedError: "request.imp[4].native.request.eventtrackers[8].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", + }, + { + description: "Methods - Many Valid", + givenEvent: nativeRequests.EventTracker{ + Event: 1, + Methods: []native1.EventTrackingMethod{1, 2}, + }, + expectedError: "", + }, + { + description: "Methods - Empty", + givenEvent: nativeRequests.EventTracker{ + Event: 1, + Methods: []native1.EventTrackingMethod{}, + }, + expectedError: "request.imp[4].native.request.eventtrackers[8].method is required. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", + }, + { + description: "Methods - Exchange Specific - Boundary", + givenEvent: nativeRequests.EventTracker{ + Event: 1, + Methods: []native1.EventTrackingMethod{500}, + }, + expectedError: "", + }, + { + description: "Methods - Exchange Specific - Boundary + 1", + givenEvent: nativeRequests.EventTracker{ + Event: 1, + Methods: []native1.EventTrackingMethod{501}, + }, + expectedError: "", + }, + { + description: "Methods - Negative", + givenEvent: nativeRequests.EventTracker{ + Event: 1, + Methods: []native1.EventTrackingMethod{-1}, + }, + expectedError: "request.imp[4].native.request.eventtrackers[8].methods[0] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", + }, + { + description: "Methods - Just Above Range", + givenEvent: nativeRequests.EventTracker{ + Event: 1, + Methods: []native1.EventTrackingMethod{3}, // Known values are currently 1-2 + }, + expectedError: "request.imp[4].native.request.eventtrackers[8].methods[0] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", + }, + { + description: "Methods - Mixed Valid + Invalid", + givenEvent: nativeRequests.EventTracker{ + Event: 1, + Methods: []native1.EventTrackingMethod{1, -1}, + }, + expectedError: "request.imp[4].native.request.eventtrackers[8].methods[1] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", + }, + } + + for _, test := range testCases { + err := validateNativeEventTracker(test.givenEvent, impIndex, eventIndex) + if test.expectedError == "" { + assert.NoError(t, err, test.description) + } else { + assert.EqualError(t, err, test.expectedError, test.description) + } + } +} + +func TestValidateNativeAssetData(t *testing.T) { + impIndex := 4 + assetIndex := 8 + + testCases := []struct { + description string + givenData nativeRequests.Data + expectedError string + }{ + { + description: "Valid", + givenData: nativeRequests.Data{Type: 1}, + expectedError: "", + }, + { + description: "Exchange Specific - Boundary", + givenData: nativeRequests.Data{Type: 500}, + expectedError: "", + }, + { + description: "Exchange Specific - Boundary + 1", + givenData: nativeRequests.Data{Type: 501}, + expectedError: "", + }, + { + description: "Not Specified", + givenData: nativeRequests.Data{}, + expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", + }, + { + description: "Negative", + givenData: nativeRequests.Data{Type: -1}, + expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", + }, + { + description: "Just Above Range", + givenData: nativeRequests.Data{Type: 13}, // Range is currently 1-12 + expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", + }, + } + + for _, test := range testCases { + err := validateNativeAssetData(&test.givenData, impIndex, assetIndex) + if test.expectedError == "" { + assert.NoError(t, err, test.description) + } else { + assert.EqualError(t, err, test.expectedError, test.description) + } + } +} diff --git a/ortb/request_validator_pmp.go b/ortb/request_validator_pmp.go new file mode 100644 index 00000000000..ca36566d9a8 --- /dev/null +++ b/ortb/request_validator_pmp.go @@ -0,0 +1,20 @@ +package ortb + +import ( + "fmt" + + "github.com/prebid/openrtb/v20/openrtb2" +) + +func validatePmp(pmp *openrtb2.PMP, impIndex int) error { + if pmp == nil { + return nil + } + + for dealIndex, deal := range pmp.Deals { + if deal.ID == "" { + return fmt.Errorf("request.imp[%d].pmp.deals[%d] missing required field: \"id\"", impIndex, dealIndex) + } + } + return nil +} diff --git a/ortb/request_validator_pmp_test.go b/ortb/request_validator_pmp_test.go new file mode 100644 index 00000000000..99f231f4892 --- /dev/null +++ b/ortb/request_validator_pmp_test.go @@ -0,0 +1,103 @@ +package ortb + +import ( + "testing" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/stretchr/testify/assert" +) + +func TestValidatePMP(t *testing.T) { + tests := []struct { + name string + pmp *openrtb2.PMP + wantError bool + }{ + { + name: "nil", + pmp: nil, + wantError: false, + }, + { + name: "nil_deals", + pmp: &openrtb2.PMP{ + Deals: nil, + }, + wantError: false, + }, + { + name: "empty_deals", + pmp: &openrtb2.PMP{ + Deals: []openrtb2.Deal{}, + }, + wantError: false, + }, + { + name: "one_deal", + pmp: &openrtb2.PMP{ + Deals: []openrtb2.Deal{ + { + ID: "deal1", + }, + }, + }, + wantError: false, + }, + { + name: "one_deal_no_id", + pmp: &openrtb2.PMP{ + Deals: []openrtb2.Deal{ + { + ID: "", + }, + }, + }, + wantError: true, + }, + { + name: "multiple_deals", + pmp: &openrtb2.PMP{ + Deals: []openrtb2.Deal{ + { + ID: "deal1", + }, + { + ID: "deal2", + }, + { + ID: "deal3", + }, + }, + }, + wantError: false, + }, + { + name: "multiple_deals_no_id", + pmp: &openrtb2.PMP{ + Deals: []openrtb2.Deal{ + { + ID: "deal1", + }, + { + ID: "", + }, + { + ID: "deal3", + }, + }, + }, + wantError: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := validatePmp(test.pmp, 1) + if test.wantError { + assert.Error(t, result) + } else { + assert.NoError(t, result) + } + }) + } +} diff --git a/ortb/request_validator_test.go b/ortb/request_validator_test.go new file mode 100644 index 00000000000..c2b6ea3c7bc --- /dev/null +++ b/ortb/request_validator_test.go @@ -0,0 +1,299 @@ +package ortb + +import ( + "encoding/json" + "errors" + "testing" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestValidateImpExt(t *testing.T) { + type testCase struct { + description string + impExt json.RawMessage + cfg ValidationConfig + paramValidatorError error + expectedImpExt string + expectedErrs []error + } + testGroups := []struct { + description string + testCases []testCase + }{ + { + "Empty", + []testCase{ + { + description: "Empty", + impExt: nil, + expectedImpExt: "", + expectedErrs: []error{errors.New("request.imp[0].ext is required")}, + }, + }, + }, + { + "Unknown bidder tests", + []testCase{ + { + description: "Unknown Bidder + Empty Prebid Bidder", + impExt: json.RawMessage(`{"prebid":{"bidder":{}}, "unknownbidder":{"placement_id":555}}`), + expectedImpExt: `{"prebid":{"bidder":{}}, "unknownbidder":{"placement_id":555}}`, + expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder")}, + }, + { + description: "Unknown Bidder only", + impExt: json.RawMessage(`{"unknownbidder":{"placement_id":555}}`), + expectedImpExt: `{"unknownbidder":{"placement_id":555}}`, + expectedErrs: []error{&errortypes.Warning{Message: ("request.imp[0].ext contains unknown bidder: 'unknownbidder', ignoring")}, + errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder")}, + }, + { + description: "Unknown Prebid Ext Bidder only", + impExt: json.RawMessage(`{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}}}`), + expectedImpExt: `{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}}}`, + expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, + }, + { + description: "Unknown Prebid Ext Bidder + First Party Data Context", + impExt: json.RawMessage(`{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`), + expectedImpExt: `{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, + }, + { + description: "Unknown Bidder + First Party Data Context", + impExt: json.RawMessage(`{"unknownbidder":{"placement_id":555} ,"context":{"data":{"keywords":"prebid server example"}}}`), + expectedImpExt: `{"unknownbidder":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{&errortypes.Warning{Message: ("request.imp[0].ext contains unknown bidder: 'unknownbidder', ignoring")}, + errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), + }, + }, + { + description: "Unknown Bidder + Disabled Bidder", + impExt: json.RawMessage(`{"unknownbidder":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`), + expectedImpExt: `{"unknownbidder":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`, + expectedErrs: []error{&errortypes.Warning{Message: ("request.imp[0].ext contains unknown bidder: 'unknownbidder', ignoring")}, + &errortypes.BidderTemporarilyDisabled{Message: ("The bidder 'disabledbidder' has been disabled.")}, + errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), + }, + }, + { + description: "Unknown Bidder + Disabled Prebid Ext Bidder", + impExt: json.RawMessage(`{"unknownbidder":{"placement_id":555},"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`), + expectedImpExt: `{"unknownbidder":{"placement_id":555},"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`, + expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: ("The bidder 'disabledbidder' has been disabled.")}, + errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), + }, + }, + }, + }, + { + "Disabled bidder tests", + []testCase{ + { + description: "Disabled Bidder", + impExt: json.RawMessage(`{"disabledbidder":{"foo":"bar"}}`), + expectedImpExt: `{"disabledbidder":{"foo":"bar"}}`, + expectedErrs: []error{ + &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, + errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), + }, + // if only bidder(s) found in request.imp[x].ext.{biddername} or request.imp[x].ext.prebid.bidder.{biddername} are disabled, return error + }, + { + description: "Disabled Prebid Ext Bidder", + impExt: json.RawMessage(`{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`), + expectedImpExt: `{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`, + expectedErrs: []error{ + &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, + errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), + }, + }, + { + description: "Disabled Bidder + First Party Data Context", + impExt: json.RawMessage(`{"disabledbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`), + expectedImpExt: `{"disabledbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{ + &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, + errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), + }, + }, + { + description: "Disabled Prebid Ext Bidder + First Party Data Context", + impExt: json.RawMessage(`{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`), + expectedImpExt: `{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{ + &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, + errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), + }, + }, + }, + }, + { + "First Party only", + []testCase{ + { + description: "First Party Data Context", + impExt: json.RawMessage(`{"context":{"data":{"keywords":"prebid server example"}}}`), + expectedImpExt: `{"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{ + errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), + }, + }, + }, + }, + { + "Valid bidder tests", + []testCase{ + { + description: "Valid bidder root ext", + impExt: json.RawMessage(`{"appnexus":{"placement_id":555}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`, + expectedErrs: []error{}, + }, + { + // Since prebid.bidder object is present - bidders expected to be within it + // So even though appnexus is a valid bidder - it is ignored and considered to be an arbitrary field + // If there was no prebid.bidder then appnexus would have been considered a bidder. + description: "Valid bidder root ext + Empty Prebid Bidder", + impExt: json.RawMessage(`{"prebid":{"bidder":{}}, "appnexus":{"placement_id":555}}`), + expectedImpExt: `{"prebid":{"bidder":{}}, "appnexus":{"placement_id":555}}`, + expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder")}, + }, + { + description: "Valid bidder in prebid field", + impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`, + expectedErrs: []error{}, + }, + { + description: "Valid Bidder + First Party Data Context", + impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{}, + }, + { + description: "Valid Prebid Ext Bidder + First Party Data Context", + impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555}}} ,"context":{"data":{"keywords":"prebid server example"}}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{}, + }, + { + description: "Valid Bidder + Unknown Bidder", + impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"unknownbidder":{"placement_id":555}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}, "unknownbidder":{"placement_id":555}}`, + expectedErrs: []error{&errortypes.Warning{Message: ("request.imp[0].ext contains unknown bidder: 'unknownbidder', ignoring")}}, + }, + { + description: "Valid Bidder + Disabled Bidder", + impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`, + expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}}, + }, + { + description: "Valid Bidder + Disabled Bidder + First Party Data Context", + impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}}, + }, + { + description: "Valid Bidder + Disabled Bidder + Unknown Bidder + First Party Data Context", + impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}, "unknownbidder":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{&errortypes.Warning{Message: ("request.imp[0].ext contains unknown bidder: 'unknownbidder', ignoring")}, + &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, + }, + }, + { + description: "Valid Prebid Ext Bidder + Disabled Prebid Bidder Ext", + impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555}}}}`, + expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}}, + }, + { + description: "Valid Prebid Ext Bidder + Arbitrary Key Ext", + impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id": 555}}},"arbitraryKey":{"placement_id":555}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555}}},"arbitraryKey":{"placement_id":555}}`, + expectedErrs: []error{}, + }, + { + description: "Valid Prebid Ext Bidder + Disabled Ext Bidder + First Party Data Context", + impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555}}},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}}, + }, + { + description: "Valid Prebid Ext Bidder + Disabled Prebid Ext Bidder + Unknown Prebid Ext + First Party Data Context", + impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, + }, + }, + }, + { + "Config tests", + []testCase{ + { + description: "Invalid Params", + impExt: json.RawMessage(`{"appnexus":{"placement_id_wrong_format":[]}}`), + cfg: ValidationConfig{ + SkipBidderParams: false, + }, + paramValidatorError: errors.New("params error"), + expectedImpExt: `{"appnexus":{"placement_id_wrong_format":[]}}`, + expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder.appnexus failed validation.\nparams error")}, + }, + { + description: "Invalid Params - Skip Params Validation", + impExt: json.RawMessage(`{"appnexus":{"placement_id_wrong_format":[]}}`), + cfg: ValidationConfig{ + SkipBidderParams: true, + }, + paramValidatorError: errors.New("params error"), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id_wrong_format":[]}}}}`, + expectedErrs: []error{}, + }, + }, + }, + } + + for _, group := range testGroups { + for _, test := range group.testCases { + t.Run(test.description, func(t *testing.T) { + imp := &openrtb2.Imp{Ext: test.impExt} + impWrapper := &openrtb_ext.ImpWrapper{Imp: imp} + + disabledBidders := map[string]string{"disabledbidder": "The bidder 'disabledbidder' has been disabled."} + rv := standardRequestValidator{ + bidderMap: openrtb_ext.BuildBidderMap(), + disabledBidders: disabledBidders, + paramsValidator: mockBidderParamValidator{ + Error: test.paramValidatorError, + }, + } + errs := rv.validateImpExt(impWrapper, test.cfg, nil, 0, false, nil) + + assert.NoError(t, impWrapper.RebuildImp(), test.description+":rebuild_imp") + + if len(test.expectedImpExt) > 0 { + assert.JSONEq(t, test.expectedImpExt, string(imp.Ext), "imp.ext JSON does not match expected. Test: %s. %s\n", group.description, test.description) + } else { + assert.Empty(t, imp.Ext, "imp.ext expected to be empty but was: %s. Test: %s. %s\n", string(imp.Ext), group.description, test.description) + } + assert.ElementsMatch(t, test.expectedErrs, errs, "errs slice does not match expected. Test: %s. %s\n", group.description, test.description) + }) + } + } +} + +type mockBidderParamValidator struct { + Error error +} + +func (v mockBidderParamValidator) Validate(name openrtb_ext.BidderName, ext json.RawMessage) error { + return v.Error +} +func (v mockBidderParamValidator) Schema(name openrtb_ext.BidderName) string { return "" } diff --git a/ortb/request_validator_video.go b/ortb/request_validator_video.go new file mode 100644 index 00000000000..4674d0c4d33 --- /dev/null +++ b/ortb/request_validator_video.go @@ -0,0 +1,34 @@ +package ortb + +import ( + "fmt" + + "github.com/prebid/openrtb/v20/openrtb2" +) + +func validateVideo(video *openrtb2.Video, impIndex int) error { + if video == nil { + return nil + } + + if len(video.MIMEs) < 1 { + return fmt.Errorf("request.imp[%d].video.mimes must contain at least one supported MIME type", impIndex) + } + + // The following fields were previously uints in the OpenRTB library we use, but have + // since been changed to ints. We decided to maintain the non-negative check. + if video.W != nil && *video.W < 0 { + return fmt.Errorf("request.imp[%d].video.w must be a positive number", impIndex) + } + if video.H != nil && *video.H < 0 { + return fmt.Errorf("request.imp[%d].video.h must be a positive number", impIndex) + } + if video.MinBitRate < 0 { + return fmt.Errorf("request.imp[%d].video.minbitrate must be a positive number", impIndex) + } + if video.MaxBitRate < 0 { + return fmt.Errorf("request.imp[%d].video.maxbitrate must be a positive number", impIndex) + } + + return nil +} diff --git a/ortb/request_validator_video_test.go b/ortb/request_validator_video_test.go new file mode 100644 index 00000000000..3d579e2c77c --- /dev/null +++ b/ortb/request_validator_video_test.go @@ -0,0 +1,111 @@ +package ortb + +import ( + "testing" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/util/ptrutil" + "github.com/stretchr/testify/assert" +) + +func TestValidateVideo(t *testing.T) { + tests := []struct { + name string + video *openrtb2.Video + wantError bool + }{ + { + name: "nil", + video: nil, + wantError: false, + }, + { + name: "well_formed", + video: &openrtb2.Video{ + MIMEs: []string{"MIME1"}, + W: ptrutil.ToPtr[int64](0), + H: ptrutil.ToPtr[int64](0), + MinBitRate: 0, + MaxBitRate: 0, + }, + wantError: false, + }, + { + name: "well_formed_with_nil_dims", + video: &openrtb2.Video{ + MIMEs: []string{"MIME1"}, + W: nil, + H: nil, + MinBitRate: 0, + MaxBitRate: 0, + }, + wantError: false, + }, + { + name: "mimes_is_zero", + video: &openrtb2.Video{ + MIMEs: []string{}, + W: ptrutil.ToPtr[int64](0), + H: ptrutil.ToPtr[int64](0), + MinBitRate: 0, + MaxBitRate: 0, + }, + wantError: true, + }, + { + name: "negative_width", + video: &openrtb2.Video{ + MIMEs: []string{"MIME1"}, + W: ptrutil.ToPtr[int64](-1), + H: ptrutil.ToPtr[int64](0), + MinBitRate: 0, + MaxBitRate: 0, + }, + wantError: true, + }, + { + name: "negative_height", + video: &openrtb2.Video{ + MIMEs: []string{"MIME1"}, + W: ptrutil.ToPtr[int64](0), + H: ptrutil.ToPtr[int64](-1), + MinBitRate: 0, + MaxBitRate: 0, + }, + wantError: true, + }, + { + name: "negative_min_bit_rate", + video: &openrtb2.Video{ + MIMEs: []string{"MIME1"}, + W: ptrutil.ToPtr[int64](0), + H: ptrutil.ToPtr[int64](0), + MinBitRate: -1, + MaxBitRate: 0, + }, + wantError: true, + }, + { + name: "negative_max_bit_rate", + video: &openrtb2.Video{ + MIMEs: []string{"MIME1"}, + W: ptrutil.ToPtr[int64](0), + H: ptrutil.ToPtr[int64](0), + MinBitRate: 0, + MaxBitRate: -1, + }, + wantError: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := validateVideo(test.video, 1) + if test.wantError { + assert.Error(t, result) + } else { + assert.NoError(t, result) + } + }) + } +} diff --git a/pbs/usersync.go b/pbs/usersync.go index a5b49f6db03..b8c0572c4ac 100644 --- a/pbs/usersync.go +++ b/pbs/usersync.go @@ -10,9 +10,9 @@ import ( "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/server/ssl" - "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/server/ssl" + "github.com/prebid/prebid-server/v3/usersync" ) // Recaptcha code from https://github.com/haisum/recaptcha/blob/master/recaptcha.go diff --git a/prebid_cache_client/client.go b/prebid_cache_client/client.go index 872420001ea..a409b4f7d1a 100644 --- a/prebid_cache_client/client.go +++ b/prebid_cache_client/client.go @@ -12,8 +12,8 @@ import ( "strings" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/metrics" "github.com/buger/jsonparser" "github.com/golang/glog" diff --git a/prebid_cache_client/client_test.go b/prebid_cache_client/client_test.go index f20d3c7829f..f00a835a4a0 100644 --- a/prebid_cache_client/client_test.go +++ b/prebid_cache_client/client_test.go @@ -10,10 +10,10 @@ import ( "strconv" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - metricsConf "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/metrics" + metricsConf "github.com/prebid/prebid-server/v3/metrics/config" + "github.com/prebid/prebid-server/v3/util/jsonutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" diff --git a/privacy/activitycontrol.go b/privacy/activitycontrol.go index 9d6668b3e44..674d7519881 100644 --- a/privacy/activitycontrol.go +++ b/privacy/activitycontrol.go @@ -1,8 +1,8 @@ package privacy import ( - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) type ActivityResult int @@ -37,7 +37,9 @@ func (r ActivityRequest) IsBidRequest() bool { } type ActivityControl struct { - plans map[Activity]ActivityPlan + plans map[Activity]ActivityPlan + IPv6Config config.IPv6 + IPv4Config config.IPv4 } func NewActivityControl(cfg *config.AccountPrivacy) ActivityControl { @@ -58,6 +60,9 @@ func NewActivityControl(cfg *config.AccountPrivacy) ActivityControl { plans[ActivityTransmitTIDs] = buildPlan(cfg.AllowActivities.TransmitTids) ac.plans = plans + ac.IPv4Config = cfg.IPv4Config + ac.IPv6Config = cfg.IPv6Config + return ac } diff --git a/privacy/activitycontrol_test.go b/privacy/activitycontrol_test.go index 743888df029..add20ad2c3e 100644 --- a/privacy/activitycontrol_test.go +++ b/privacy/activitycontrol_test.go @@ -3,9 +3,9 @@ package privacy import ( "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" ) @@ -33,17 +33,23 @@ func TestNewActivityControl(t *testing.T) { TransmitUniqueRequestIds: getTestActivityConfig(true), TransmitTids: getTestActivityConfig(true), }, + IPv6Config: config.IPv6{AnonKeepBits: 32}, + IPv4Config: config.IPv4{AnonKeepBits: 16}, + }, + activityControl: ActivityControl{ + plans: map[Activity]ActivityPlan{ + ActivitySyncUser: getTestActivityPlan(ActivityAllow), + ActivityFetchBids: getTestActivityPlan(ActivityAllow), + ActivityEnrichUserFPD: getTestActivityPlan(ActivityAllow), + ActivityReportAnalytics: getTestActivityPlan(ActivityAllow), + ActivityTransmitUserFPD: getTestActivityPlan(ActivityAllow), + ActivityTransmitPreciseGeo: getTestActivityPlan(ActivityDeny), + ActivityTransmitUniqueRequestIDs: getTestActivityPlan(ActivityAllow), + ActivityTransmitTIDs: getTestActivityPlan(ActivityAllow), + }, + IPv6Config: config.IPv6{AnonKeepBits: 32}, + IPv4Config: config.IPv4{AnonKeepBits: 16}, }, - activityControl: ActivityControl{plans: map[Activity]ActivityPlan{ - ActivitySyncUser: getTestActivityPlan(ActivityAllow), - ActivityFetchBids: getTestActivityPlan(ActivityAllow), - ActivityEnrichUserFPD: getTestActivityPlan(ActivityAllow), - ActivityReportAnalytics: getTestActivityPlan(ActivityAllow), - ActivityTransmitUserFPD: getTestActivityPlan(ActivityAllow), - ActivityTransmitPreciseGeo: getTestActivityPlan(ActivityDeny), - ActivityTransmitUniqueRequestIDs: getTestActivityPlan(ActivityAllow), - ActivityTransmitTIDs: getTestActivityPlan(ActivityAllow), - }}, }, } diff --git a/privacy/ccpa/consentwriter.go b/privacy/ccpa/consentwriter.go index 1d65a272f90..3a0934c5722 100644 --- a/privacy/ccpa/consentwriter.go +++ b/privacy/ccpa/consentwriter.go @@ -1,8 +1,7 @@ package ccpa import ( - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" ) // ConsentWriter implements the old PolicyWriter interface for CCPA. @@ -16,16 +15,14 @@ func (c ConsentWriter) Write(req *openrtb2.BidRequest) error { if req == nil { return nil } - reqWrap := &openrtb_ext.RequestWrapper{BidRequest: req} // Set consent string in USPrivacy if c.Consent != "" { - if regsExt, err := reqWrap.GetRegExt(); err == nil { - regsExt.SetUSPrivacy(c.Consent) - } else { - return err + if req.Regs == nil { + req.Regs = &openrtb2.Regs{} } + req.Regs.USPrivacy = c.Consent } - return reqWrap.RebuildRequest() + return nil } diff --git a/privacy/ccpa/consentwriter_test.go b/privacy/ccpa/consentwriter_test.go index 015f1328f61..6d0b89e24c2 100644 --- a/privacy/ccpa/consentwriter_test.go +++ b/privacy/ccpa/consentwriter_test.go @@ -4,8 +4,8 @@ import ( "encoding/json" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -75,7 +75,9 @@ func TestConsentWriterLegacy(t *testing.T) { description: "Success", request: &openrtb2.BidRequest{}, expected: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + Regs: &openrtb2.Regs{ + USPrivacy: "anyConsent", + }, }, }, { @@ -83,9 +85,12 @@ func TestConsentWriterLegacy(t *testing.T) { request: &openrtb2.BidRequest{ Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)}, }, - expectedError: true, + expectedError: false, expected: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)}, + Regs: &openrtb2.Regs{ + USPrivacy: "anyConsent", + Ext: json.RawMessage(`malformed}`), + }, }, }, } diff --git a/privacy/ccpa/parsedpolicy.go b/privacy/ccpa/parsedpolicy.go index 7b9c2d1fa7c..98b04a16322 100644 --- a/privacy/ccpa/parsedpolicy.go +++ b/privacy/ccpa/parsedpolicy.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/v3/errortypes" ) const ( diff --git a/privacy/ccpa/policy.go b/privacy/ccpa/policy.go index fbafd8a8a2e..b4841a174f8 100644 --- a/privacy/ccpa/policy.go +++ b/privacy/ccpa/policy.go @@ -6,10 +6,10 @@ import ( gpplib "github.com/prebid/go-gpp" gppConstants "github.com/prebid/go-gpp/constants" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" - gppPolicy "github.com/prebid/prebid-server/privacy/gpp" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + gppPolicy "github.com/prebid/prebid-server/v3/privacy/gpp" ) // Policy represents the CCPA regulatory information from an OpenRTB bid request. @@ -41,15 +41,8 @@ func ReadFromRequestWrapper(req *openrtb_ext.RequestWrapper, gpp gpplib.GppConta WarningCode: errortypes.InvalidPrivacyConsentWarningCode} } - if consent == "" { - // Read consent from request.regs.ext - regsExt, err := req.GetRegExt() - if err != nil { - return Policy{}, fmt.Errorf("error reading request.regs.ext: %s", err) - } - if regsExt != nil { - consent = regsExt.GetUSPrivacy() - } + if consent == "" && req.Regs != nil { + consent = req.Regs.USPrivacy } // Read no sale bidders from request.ext.prebid reqExt, err := req.GetRequestExt() @@ -75,21 +68,19 @@ func ReadFromRequest(req *openrtb2.BidRequest) (Policy, error) { // Write mutates an OpenRTB bid request with the CCPA regulatory information. func (p Policy) Write(req *openrtb_ext.RequestWrapper) error { - if req == nil { + if req == nil || req.BidRequest == nil { return nil } - regsExt, err := req.GetRegExt() - if err != nil { - return err - } - reqExt, err := req.GetRequestExt() if err != nil { return err } - regsExt.SetUSPrivacy(p.Consent) + if req.Regs == nil { + req.Regs = &openrtb2.Regs{} + } + req.Regs.USPrivacy = p.Consent setPrebidNoSale(p.NoSaleBidders, reqExt) return nil } diff --git a/privacy/ccpa/policy_test.go b/privacy/ccpa/policy_test.go index 3a1433333c0..89bb2ccf040 100644 --- a/privacy/ccpa/policy_test.go +++ b/privacy/ccpa/policy_test.go @@ -7,8 +7,8 @@ import ( gpplib "github.com/prebid/go-gpp" gppConstants "github.com/prebid/go-gpp/constants" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -23,7 +23,7 @@ func TestReadFromRequestWrapper(t *testing.T) { { description: "Success", request: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + Regs: &openrtb2.Regs{USPrivacy: "ABC"}, Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedPolicy: Policy{ @@ -83,26 +83,10 @@ func TestReadFromRequestWrapper(t *testing.T) { NoSaleBidders: []string{"a", "b"}, }, }, - { - description: "Malformed Regs.Ext", - request: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)}, - Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), - }, - expectedError: true, - }, - { - description: "Invalid Regs.Ext Type", - request: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123`)}, - Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), - }, - expectedError: true, - }, { description: "Nil Ext", request: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + Regs: &openrtb2.Regs{USPrivacy: "ABC"}, Ext: nil, }, expectedPolicy: Policy{ @@ -113,7 +97,7 @@ func TestReadFromRequestWrapper(t *testing.T) { { description: "Empty Ext", request: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + Regs: &openrtb2.Regs{USPrivacy: "ABC"}, Ext: json.RawMessage(`{}`), }, expectedPolicy: Policy{ @@ -124,7 +108,7 @@ func TestReadFromRequestWrapper(t *testing.T) { { description: "Missing Ext.Prebid No Sale Value", request: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + Regs: &openrtb2.Regs{USPrivacy: "ABC"}, Ext: json.RawMessage(`{"anythingElse":"42"}`), }, expectedPolicy: Policy{ @@ -148,15 +132,6 @@ func TestReadFromRequestWrapper(t *testing.T) { }, expectedError: true, }, - { - description: "Injection Attack", - request: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`)}, - }, - expectedPolicy: Policy{ - Consent: "1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", - }, - }, { description: "GPP Success", request: &openrtb2.BidRequest{ @@ -244,7 +219,7 @@ func TestReadFromRequest(t *testing.T) { { description: "Success", request: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + Regs: &openrtb2.Regs{USPrivacy: "ABC"}, Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedPolicy: Policy{ @@ -353,7 +328,7 @@ func TestWrite(t *testing.T) { policy: Policy{Consent: "anyConsent", NoSaleBidders: []string{"a", "b"}}, request: &openrtb2.BidRequest{}, expected: &openrtb2.BidRequest{ - Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + Regs: &openrtb2.Regs{USPrivacy: "anyConsent"}, Ext: json.RawMessage(`{"prebid":{"nosale":["a","b"]}}`), }, }, @@ -922,3 +897,7 @@ func (ms mockGPPSection) GetID() gppConstants.SectionID { func (ms mockGPPSection) GetValue() string { return ms.value } + +func (ms mockGPPSection) Encode(bool) []byte { + return nil +} diff --git a/privacy/enforcement.go b/privacy/enforcement.go deleted file mode 100644 index 8074d96acf3..00000000000 --- a/privacy/enforcement.go +++ /dev/null @@ -1,92 +0,0 @@ -package privacy - -import ( - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/config" -) - -// Enforcement represents the privacy policies to enforce for an OpenRTB bid request. -type Enforcement struct { - CCPA bool - COPPA bool - GDPRGeo bool - GDPRID bool - LMT bool - - // activities - UFPD bool - Eids bool - PreciseGeo bool - TID bool -} - -// Any returns true if at least one privacy policy requires enforcement. -func (e Enforcement) AnyLegacy() bool { - return e.CCPA || e.COPPA || e.GDPRGeo || e.GDPRID || e.LMT -} - -func (e Enforcement) AnyActivities() bool { - return e.UFPD || e.PreciseGeo || e.Eids || e.TID -} - -// Apply cleans personally identifiable information from an OpenRTB bid request. -func (e Enforcement) Apply(bidRequest *openrtb2.BidRequest, privacy config.AccountPrivacy) { - e.apply(bidRequest, NewScrubber(privacy.IPv6Config, privacy.IPv4Config)) -} - -func (e Enforcement) apply(bidRequest *openrtb2.BidRequest, scrubber Scrubber) { - if bidRequest != nil { - if e.AnyActivities() { - bidRequest = scrubber.ScrubRequest(bidRequest, e) - } - if e.AnyLegacy() && !(e.UFPD && e.PreciseGeo && e.Eids) { - bidRequest.User = scrubber.ScrubUser(bidRequest.User, e.getUserScrubStrategy(), e.getGeoScrubStrategy()) - } - if e.AnyLegacy() && !(e.UFPD && e.PreciseGeo) { - bidRequest.Device = scrubber.ScrubDevice(bidRequest.Device, e.getDeviceIDScrubStrategy(), e.getIPv4ScrubStrategy(), e.getIPv6ScrubStrategy(), e.getGeoScrubStrategy()) - } - } -} - -func (e Enforcement) getDeviceIDScrubStrategy() ScrubStrategyDeviceID { - if e.COPPA || e.GDPRID || e.CCPA || e.LMT { - return ScrubStrategyDeviceIDAll - } - - return ScrubStrategyDeviceIDNone -} - -func (e Enforcement) getIPv4ScrubStrategy() ScrubStrategyIPV4 { - if e.COPPA || e.GDPRGeo || e.CCPA || e.LMT { - return ScrubStrategyIPV4Subnet - } - - return ScrubStrategyIPV4None -} - -func (e Enforcement) getIPv6ScrubStrategy() ScrubStrategyIPV6 { - if e.GDPRGeo || e.CCPA || e.LMT || e.COPPA { - return ScrubStrategyIPV6Subnet - } - return ScrubStrategyIPV6None -} - -func (e Enforcement) getGeoScrubStrategy() ScrubStrategyGeo { - if e.COPPA { - return ScrubStrategyGeoFull - } - - if e.GDPRGeo || e.CCPA || e.LMT { - return ScrubStrategyGeoReducedPrecision - } - - return ScrubStrategyGeoNone -} - -func (e Enforcement) getUserScrubStrategy() ScrubStrategyUser { - if e.COPPA || e.CCPA || e.LMT || e.GDPRID { - return ScrubStrategyUserIDAndDemographic - } - - return ScrubStrategyUserNone -} diff --git a/privacy/enforcement_test.go b/privacy/enforcement_test.go deleted file mode 100644 index a97779eb903..00000000000 --- a/privacy/enforcement_test.go +++ /dev/null @@ -1,393 +0,0 @@ -package privacy - -import ( - "testing" - - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func TestAnyLegacy(t *testing.T) { - testCases := []struct { - enforcement Enforcement - expected bool - description string - }{ - { - description: "All False", - enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPRGeo: false, - GDPRID: false, - LMT: false, - }, - expected: false, - }, - { - description: "All True", - enforcement: Enforcement{ - CCPA: true, - COPPA: true, - GDPRGeo: true, - GDPRID: true, - LMT: true, - }, - expected: true, - }, - { - description: "Mixed", - enforcement: Enforcement{ - CCPA: false, - COPPA: true, - GDPRGeo: false, - GDPRID: false, - LMT: true, - }, - expected: true, - }, - } - - for _, test := range testCases { - result := test.enforcement.AnyLegacy() - assert.Equal(t, test.expected, result, test.description) - } -} - -func TestApplyGDPR(t *testing.T) { - testCases := []struct { - description string - enforcement Enforcement - expectedDeviceID ScrubStrategyDeviceID - expectedDeviceIPv4 ScrubStrategyIPV4 - expectedDeviceIPv6 ScrubStrategyIPV6 - expectedDeviceGeo ScrubStrategyGeo - expectedUser ScrubStrategyUser - expectedUserGeo ScrubStrategyGeo - }{ - { - description: "All Enforced", - enforcement: Enforcement{ - CCPA: true, - COPPA: true, - GDPRGeo: true, - GDPRID: true, - LMT: true, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Subnet, - expectedDeviceIPv6: ScrubStrategyIPV6Subnet, - expectedDeviceGeo: ScrubStrategyGeoFull, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoFull, - }, - { - description: "CCPA Only", - enforcement: Enforcement{ - CCPA: true, - COPPA: false, - GDPRGeo: false, - GDPRID: false, - LMT: false, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Subnet, - expectedDeviceIPv6: ScrubStrategyIPV6Subnet, - expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "COPPA Only", - enforcement: Enforcement{ - CCPA: false, - COPPA: true, - GDPRGeo: false, - GDPRID: false, - LMT: false, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Subnet, - expectedDeviceIPv6: ScrubStrategyIPV6Subnet, - expectedDeviceGeo: ScrubStrategyGeoFull, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoFull, - }, - { - description: "GDPR Only - Full", - enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPRGeo: true, - GDPRID: true, - LMT: false, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Subnet, - expectedDeviceIPv6: ScrubStrategyIPV6Subnet, - expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "GDPR Only - ID Only", - enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPRGeo: false, - GDPRID: true, - LMT: false, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4None, - expectedDeviceIPv6: ScrubStrategyIPV6None, - expectedDeviceGeo: ScrubStrategyGeoNone, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoNone, - }, - { - description: "GDPR Only - Geo Only", - enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPRGeo: true, - GDPRID: false, - LMT: false, - }, - expectedDeviceID: ScrubStrategyDeviceIDNone, - expectedDeviceIPv4: ScrubStrategyIPV4Subnet, - expectedDeviceIPv6: ScrubStrategyIPV6Subnet, - expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, - expectedUser: ScrubStrategyUserNone, - expectedUserGeo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "LMT Only", - enforcement: Enforcement{ - CCPA: false, - COPPA: false, - GDPRGeo: false, - GDPRID: false, - LMT: true, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Subnet, - expectedDeviceIPv6: ScrubStrategyIPV6Subnet, - expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "Interactions: COPPA + GDPR Full", - enforcement: Enforcement{ - CCPA: false, - COPPA: true, - GDPRGeo: true, - GDPRID: true, - LMT: false, - }, - expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Subnet, - expectedDeviceIPv6: ScrubStrategyIPV6Subnet, - expectedDeviceGeo: ScrubStrategyGeoFull, - expectedUser: ScrubStrategyUserIDAndDemographic, - expectedUserGeo: ScrubStrategyGeoFull, - }, - } - - for _, test := range testCases { - req := &openrtb2.BidRequest{ - Device: &openrtb2.Device{}, - User: &openrtb2.User{}, - } - replacedDevice := &openrtb2.Device{} - replacedUser := &openrtb2.User{} - - m := &mockScrubber{} - m.On("ScrubDevice", req.Device, test.expectedDeviceID, test.expectedDeviceIPv4, test.expectedDeviceIPv6, test.expectedDeviceGeo).Return(replacedDevice).Once() - m.On("ScrubUser", req.User, test.expectedUser, test.expectedUserGeo).Return(replacedUser).Once() - - test.enforcement.apply(req, m) - - m.AssertExpectations(t) - assert.Same(t, replacedDevice, req.Device, "Device") - assert.Same(t, replacedUser, req.User, "User") - } -} - -func TestApplyToggle(t *testing.T) { - testCases := []struct { - description string - enforcement Enforcement - expectedScrubRequestExecuted bool - expectedScrubUserExecuted bool - expectedScrubDeviceExecuted bool - }{ - { - description: "All enforced - only ScrubRequest execution expected", - enforcement: Enforcement{ - CCPA: true, - COPPA: true, - GDPRGeo: true, - GDPRID: true, - LMT: true, - UFPD: true, - Eids: true, - PreciseGeo: true, - TID: true, - }, - expectedScrubRequestExecuted: true, - expectedScrubUserExecuted: false, - expectedScrubDeviceExecuted: false, - }, - { - description: "All Legacy and no activities - ScrubUser and ScrubDevice execution expected", - enforcement: Enforcement{ - CCPA: true, - COPPA: true, - GDPRGeo: true, - GDPRID: true, - LMT: true, - UFPD: false, - Eids: false, - PreciseGeo: false, - TID: false, - }, - expectedScrubRequestExecuted: false, - expectedScrubUserExecuted: true, - expectedScrubDeviceExecuted: true, - }, - { - description: "Some Legacy and some activities - ScrubRequest, ScrubUser and ScrubDevice execution expected", - enforcement: Enforcement{ - CCPA: true, - COPPA: true, - GDPRGeo: true, - GDPRID: true, - LMT: true, - UFPD: true, - Eids: false, - PreciseGeo: false, - TID: false, - }, - expectedScrubRequestExecuted: true, - expectedScrubUserExecuted: true, - expectedScrubDeviceExecuted: true, - }, - { - description: "Some Legacy and some activities - ScrubRequest execution expected", - enforcement: Enforcement{ - CCPA: true, - COPPA: true, - GDPRGeo: true, - GDPRID: true, - LMT: true, - UFPD: true, - Eids: true, - PreciseGeo: true, - TID: false, - }, - expectedScrubRequestExecuted: true, - expectedScrubUserExecuted: false, - expectedScrubDeviceExecuted: false, - }, - { - description: "Some Legacy and some activities overlap - ScrubRequest and ScrubUser execution expected", - enforcement: Enforcement{ - CCPA: true, - COPPA: true, - GDPRGeo: true, - GDPRID: true, - LMT: true, - UFPD: true, - Eids: false, - PreciseGeo: true, - TID: false, - }, - expectedScrubRequestExecuted: true, - expectedScrubUserExecuted: true, - expectedScrubDeviceExecuted: false, - }, - } - - for _, test := range testCases { - t.Run(test.description, func(t *testing.T) { - req := &openrtb2.BidRequest{ - Device: &openrtb2.Device{}, - User: &openrtb2.User{}, - } - replacedDevice := &openrtb2.Device{} - replacedUser := &openrtb2.User{} - - m := &mockScrubber{} - - if test.expectedScrubRequestExecuted { - m.On("ScrubRequest", req, test.enforcement).Return(req).Once() - } - if test.expectedScrubUserExecuted { - m.On("ScrubUser", req.User, ScrubStrategyUserIDAndDemographic, ScrubStrategyGeoFull).Return(replacedUser).Once() - } - if test.expectedScrubDeviceExecuted { - m.On("ScrubDevice", req.Device, ScrubStrategyDeviceIDAll, ScrubStrategyIPV4Subnet, ScrubStrategyIPV6Subnet, ScrubStrategyGeoFull).Return(replacedDevice).Once() - } - - test.enforcement.apply(req, m) - - m.AssertExpectations(t) - - }) - } -} - -func TestApplyNoneApplicable(t *testing.T) { - req := &openrtb2.BidRequest{} - - m := &mockScrubber{} - - enforcement := Enforcement{ - CCPA: false, - COPPA: false, - GDPRGeo: false, - GDPRID: false, - LMT: false, - - UFPD: false, - PreciseGeo: false, - TID: false, - Eids: false, - } - enforcement.apply(req, m) - - m.AssertNotCalled(t, "ScrubDevice") - m.AssertNotCalled(t, "ScrubUser") -} - -func TestApplyNil(t *testing.T) { - m := &mockScrubber{} - - enforcement := Enforcement{} - enforcement.apply(nil, m) - - m.AssertNotCalled(t, "ScrubDevice") - m.AssertNotCalled(t, "ScrubUser") -} - -type mockScrubber struct { - mock.Mock -} - -func (m *mockScrubber) ScrubRequest(bidRequest *openrtb2.BidRequest, enforcement Enforcement) *openrtb2.BidRequest { - args := m.Called(bidRequest, enforcement) - return args.Get(0).(*openrtb2.BidRequest) -} - -func (m *mockScrubber) ScrubDevice(device *openrtb2.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb2.Device { - args := m.Called(device, id, ipv4, ipv6, geo) - return args.Get(0).(*openrtb2.Device) -} - -func (m *mockScrubber) ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb2.User { - args := m.Called(user, strategy, geo) - return args.Get(0).(*openrtb2.User) -} diff --git a/privacy/gdpr/consentwriter.go b/privacy/gdpr/consentwriter.go index 00e3558fd40..8269352355d 100644 --- a/privacy/gdpr/consentwriter.go +++ b/privacy/gdpr/consentwriter.go @@ -1,14 +1,13 @@ package gdpr import ( - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" ) // ConsentWriter implements the PolicyWriter interface for GDPR TCF. type ConsentWriter struct { - Consent string - RegExtGDPR *int8 + Consent string + GDPR *int8 } // Write mutates an OpenRTB bid request with the GDPR TCF consent. @@ -16,26 +15,19 @@ func (c ConsentWriter) Write(req *openrtb2.BidRequest) error { if req == nil { return nil } - reqWrap := &openrtb_ext.RequestWrapper{BidRequest: req} - if c.RegExtGDPR != nil { - if regsExt, err := reqWrap.GetRegExt(); err == nil { - regsExt.SetGDPR(c.RegExtGDPR) - } else { - return err + if c.GDPR != nil { + if req.Regs == nil { + req.Regs = &openrtb2.Regs{} } + req.Regs.GDPR = c.GDPR } if c.Consent != "" { - if userExt, err := reqWrap.GetUserExt(); err == nil { - userExt.SetConsent(&c.Consent) - } else { - return err + if req.User == nil { + req.User = &openrtb2.User{} } - } - - if err := reqWrap.RebuildRequest(); err != nil { - return err + req.User.Consent = c.Consent } return nil diff --git a/privacy/gdpr/consentwriter_test.go b/privacy/gdpr/consentwriter_test.go index d90de4a2405..436f46dd563 100644 --- a/privacy/gdpr/consentwriter_test.go +++ b/privacy/gdpr/consentwriter_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" "github.com/stretchr/testify/assert" ) @@ -27,14 +27,14 @@ func TestConsentWriter(t *testing.T) { consent: "anyConsent", request: &openrtb2.BidRequest{}, expected: &openrtb2.BidRequest{User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent":"anyConsent"}`)}}, + Consent: "anyConsent"}}, }, { description: "Enabled With Nil Request User Ext Object", consent: "anyConsent", request: &openrtb2.BidRequest{User: &openrtb2.User{}}, expected: &openrtb2.BidRequest{User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent":"anyConsent"}`)}}, + Consent: "anyConsent"}}, }, { description: "Enabled With Existing Request User Ext Object - Doesn't Overwrite", @@ -42,29 +42,25 @@ func TestConsentWriter(t *testing.T) { request: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"existing":"any"}`)}}, expected: &openrtb2.BidRequest{User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent":"anyConsent","existing":"any"}`)}}, + Consent: "anyConsent", + Ext: json.RawMessage(`{"existing":"any"}`)}}, }, { description: "Enabled With Existing Request User Ext Object - Overwrites", consent: "anyConsent", request: &openrtb2.BidRequest{User: &openrtb2.User{ - Ext: json.RawMessage(`{"existing":"any","consent":"toBeOverwritten"}`)}}, + Consent: "toBeOverwritten", + Ext: json.RawMessage(`{"existing":"any"}`)}}, expected: &openrtb2.BidRequest{User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent":"anyConsent","existing":"any"}`)}}, - }, - { - description: "Enabled With Existing Malformed Request User Ext Object", - consent: "anyConsent", - request: &openrtb2.BidRequest{User: &openrtb2.User{ - Ext: json.RawMessage(`malformed`)}}, - expectedError: true, + Consent: "anyConsent", + Ext: json.RawMessage(`{"existing":"any"}`)}}, }, { description: "Injection Attack With Nil Request User Object", consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", request: &openrtb2.BidRequest{}, expected: &openrtb2.BidRequest{User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`), + Consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", }}, }, { @@ -72,7 +68,8 @@ func TestConsentWriter(t *testing.T) { consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", request: &openrtb2.BidRequest{User: &openrtb2.User{}}, expected: &openrtb2.BidRequest{User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`), + Consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", + Ext: nil, }}, }, { @@ -82,7 +79,8 @@ func TestConsentWriter(t *testing.T) { Ext: json.RawMessage(`{"existing":"any"}`), }}, expected: &openrtb2.BidRequest{User: &openrtb2.User{ - Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"","existing":"any"}`), + Consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", + Ext: json.RawMessage(`{"existing":"any"}`), }}, }, } diff --git a/privacy/lmt/ios.go b/privacy/lmt/ios.go index 0b308a9ce32..7778a02d0a5 100644 --- a/privacy/lmt/ios.go +++ b/privacy/lmt/ios.go @@ -3,9 +3,9 @@ package lmt import ( "strings" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/iosutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/iosutil" ) var ( diff --git a/privacy/lmt/ios_test.go b/privacy/lmt/ios_test.go index 2a679bfbd99..42636759eb1 100644 --- a/privacy/lmt/ios_test.go +++ b/privacy/lmt/ios_test.go @@ -4,8 +4,8 @@ import ( "encoding/json" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/util/iosutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/util/iosutil" "github.com/stretchr/testify/assert" ) diff --git a/privacy/lmt/policy.go b/privacy/lmt/policy.go index 0272a4f96b9..b4ab10394fc 100644 --- a/privacy/lmt/policy.go +++ b/privacy/lmt/policy.go @@ -1,6 +1,6 @@ package lmt -import "github.com/prebid/openrtb/v19/openrtb2" +import "github.com/prebid/openrtb/v20/openrtb2" const ( trackingUnrestricted = 0 diff --git a/privacy/lmt/policy_test.go b/privacy/lmt/policy_test.go index 222f7aca0d1..29e9370b8c1 100644 --- a/privacy/lmt/policy_test.go +++ b/privacy/lmt/policy_test.go @@ -3,7 +3,7 @@ package lmt import ( "testing" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" "github.com/stretchr/testify/assert" ) diff --git a/privacy/rule_condition_test.go b/privacy/rule_condition_test.go index bb1d81c00d2..97bdeef35fd 100644 --- a/privacy/rule_condition_test.go +++ b/privacy/rule_condition_test.go @@ -3,8 +3,8 @@ package privacy import ( "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/privacy/scrubber.go b/privacy/scrubber.go index 0cfc7cdd7f4..8f4a9b103f6 100644 --- a/privacy/scrubber.go +++ b/privacy/scrubber.go @@ -4,250 +4,146 @@ import ( "encoding/json" "net" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/util/iputil" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/ptrutil" + "github.com/prebid/prebid-server/v3/util/jsonutil" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/iputil" ) -// ScrubStrategyIPV4 defines the approach to scrub PII from an IPV4 address. -type ScrubStrategyIPV4 int - -const ( - // ScrubStrategyIPV4None does not remove any part of an IPV4 address. - ScrubStrategyIPV4None ScrubStrategyIPV4 = iota - - // ScrubStrategyIPV4Subnet zeroes out the last 8 bits of an IPV4 address. - ScrubStrategyIPV4Subnet -) - -// ScrubStrategyIPV6 defines the approach to scrub PII from an IPV6 address. -type ScrubStrategyIPV6 int - -const ( - // ScrubStrategyIPV6None does not remove any part of an IPV6 address. - ScrubStrategyIPV6None ScrubStrategyIPV6 = iota - - // ScrubStrategyIPV6Subnet zeroes out the last 16 bits of an IPV6 sub net address. - ScrubStrategyIPV6Subnet -) - -// ScrubStrategyGeo defines the approach to scrub PII from geographical data. -type ScrubStrategyGeo int - -const ( - // ScrubStrategyGeoNone does not remove any geographical data. - ScrubStrategyGeoNone ScrubStrategyGeo = iota - - // ScrubStrategyGeoFull removes all geographical data. - ScrubStrategyGeoFull - - // ScrubStrategyGeoReducedPrecision anonymizes geographical data with rounding. - ScrubStrategyGeoReducedPrecision -) - -// ScrubStrategyUser defines the approach to scrub PII from user data. -type ScrubStrategyUser int - -const ( - // ScrubStrategyUserNone does not remove non-location data. - ScrubStrategyUserNone ScrubStrategyUser = iota - - // ScrubStrategyUserIDAndDemographic removes the user's buyer id, exchange id year of birth, and gender. - ScrubStrategyUserIDAndDemographic -) - -// ScrubStrategyDeviceID defines the approach to remove hardware id and device id data. -type ScrubStrategyDeviceID int - -const ( - // ScrubStrategyDeviceIDNone does not remove hardware id and device id data. - ScrubStrategyDeviceIDNone ScrubStrategyDeviceID = iota - - // ScrubStrategyDeviceIDAll removes all hardware and device id data (ifa, mac hashes device id hashes) - ScrubStrategyDeviceIDAll -) - -// Scrubber removes PII from parts of an OpenRTB request. -type Scrubber interface { - ScrubRequest(bidRequest *openrtb2.BidRequest, enforcement Enforcement) *openrtb2.BidRequest - ScrubDevice(device *openrtb2.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb2.Device - ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb2.User +type IPConf struct { + IPV6 config.IPv6 + IPV4 config.IPv4 } -type scrubber struct { - ipV6 config.IPv6 - ipV4 config.IPv4 +func scrubDeviceIDs(reqWrapper *openrtb_ext.RequestWrapper) { + if reqWrapper.Device != nil { + reqWrapper.Device.DIDMD5 = "" + reqWrapper.Device.DIDSHA1 = "" + reqWrapper.Device.DPIDMD5 = "" + reqWrapper.Device.DPIDSHA1 = "" + reqWrapper.Device.IFA = "" + reqWrapper.Device.MACMD5 = "" + reqWrapper.Device.MACSHA1 = "" + } } -// NewScrubber returns an OpenRTB scrubber. -func NewScrubber(ipV6 config.IPv6, ipV4 config.IPv4) Scrubber { - return scrubber{ - ipV6: ipV6, - ipV4: ipV4, +func scrubUserIDs(reqWrapper *openrtb_ext.RequestWrapper) { + if reqWrapper.User != nil { + reqWrapper.User.Data = nil + reqWrapper.User.ID = "" + reqWrapper.User.BuyerUID = "" + reqWrapper.User.Yob = 0 + reqWrapper.User.Gender = "" + reqWrapper.User.Keywords = "" + reqWrapper.User.KwArray = nil } } -func (s scrubber) ScrubRequest(bidRequest *openrtb2.BidRequest, enforcement Enforcement) *openrtb2.BidRequest { - var userExtParsed map[string]json.RawMessage - userExtModified := false - - // expressed in two lines because IntelliJ cannot infer the generic type - var userCopy *openrtb2.User - userCopy = ptrutil.Clone(bidRequest.User) - - // expressed in two lines because IntelliJ cannot infer the generic type - var deviceCopy *openrtb2.Device - deviceCopy = ptrutil.Clone(bidRequest.Device) - - if userCopy != nil && (enforcement.UFPD || enforcement.Eids) { - if len(userCopy.Ext) != 0 { - jsonutil.Unmarshal(userCopy.Ext, &userExtParsed) - } +func scrubUserDemographics(reqWrapper *openrtb_ext.RequestWrapper) { + if reqWrapper.User != nil { + reqWrapper.User.BuyerUID = "" + reqWrapper.User.ID = "" + reqWrapper.User.Yob = 0 + reqWrapper.User.Gender = "" } +} - if enforcement.UFPD { - // transmitUfpd covers user.ext.data, user.data, user.id, user.buyeruid, user.yob, user.gender, user.keywords, user.kwarray - // and device.{ifa, macsha1, macmd5, dpidsha1, dpidmd5, didsha1, didmd5} - if deviceCopy != nil { - deviceCopy.DIDMD5 = "" - deviceCopy.DIDSHA1 = "" - deviceCopy.DPIDMD5 = "" - deviceCopy.DPIDSHA1 = "" - deviceCopy.IFA = "" - deviceCopy.MACMD5 = "" - deviceCopy.MACSHA1 = "" - } - if userCopy != nil { - userCopy.Data = nil - userCopy.ID = "" - userCopy.BuyerUID = "" - userCopy.Yob = 0 - userCopy.Gender = "" - userCopy.Keywords = "" - userCopy.KwArray = nil - - _, hasField := userExtParsed["data"] - if hasField { - delete(userExtParsed, "data") - userExtModified = true - } +func scrubUserExt(reqWrapper *openrtb_ext.RequestWrapper, fieldName string) error { + if reqWrapper.User != nil { + userExt, err := reqWrapper.GetUserExt() + if err != nil { + return err } - } - if enforcement.Eids { - //transmitEids covers user.eids and user.ext.eids - if userCopy != nil { - userCopy.EIDs = nil - _, hasField := userExtParsed["eids"] - if hasField { - delete(userExtParsed, "eids") - userExtModified = true - } + ext := userExt.GetExt() + _, hasField := ext[fieldName] + if hasField { + delete(ext, fieldName) + userExt.SetExt(ext) } } + return nil +} - if userExtModified { - userExt, _ := jsonutil.Marshal(userExtParsed) - userCopy.Ext = userExt +func ScrubEIDs(reqWrapper *openrtb_ext.RequestWrapper) error { + //transmitEids removes user.eids and user.ext.eids + if reqWrapper.User != nil { + reqWrapper.User.EIDs = nil } + return scrubUserExt(reqWrapper, "eids") +} - if enforcement.TID { - //remove source.tid and imp.ext.tid - if bidRequest.Source != nil { - sourceCopy := ptrutil.Clone(bidRequest.Source) - sourceCopy.TID = "" - bidRequest.Source = sourceCopy - } - for ind, imp := range bidRequest.Imp { - impExt := scrubExtIDs(imp.Ext, "tid") - bidRequest.Imp[ind].Ext = impExt - } +func ScrubTID(reqWrapper *openrtb_ext.RequestWrapper) { + if reqWrapper.Source != nil { + reqWrapper.Source.TID = "" } - - if enforcement.PreciseGeo { - //round user's geographic location by rounding off IP address and lat/lng data. - //this applies to both device.geo and user.geo - if userCopy != nil && userCopy.Geo != nil { - userCopy.Geo = scrubGeoPrecision(userCopy.Geo) - } - - if deviceCopy != nil { - if deviceCopy.Geo != nil { - deviceCopy.Geo = scrubGeoPrecision(deviceCopy.Geo) - } - deviceCopy.IP = scrubIP(deviceCopy.IP, s.ipV4.AnonKeepBits, iputil.IPv4BitSize) - deviceCopy.IPv6 = scrubIP(deviceCopy.IPv6, s.ipV6.AnonKeepBits, iputil.IPv6BitSize) - } + impWrapper := reqWrapper.GetImp() + for ind, imp := range impWrapper { + impExt := scrubExtIDs(imp.Ext, "tid") + impWrapper[ind].Ext = impExt } - - bidRequest.Device = deviceCopy - bidRequest.User = userCopy - return bidRequest + reqWrapper.SetImp(impWrapper) } -func (s scrubber) ScrubDevice(device *openrtb2.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb2.Device { - if device == nil { - return nil - } - - deviceCopy := *device - - switch id { - case ScrubStrategyDeviceIDAll: - deviceCopy.DIDMD5 = "" - deviceCopy.DIDSHA1 = "" - deviceCopy.DPIDMD5 = "" - deviceCopy.DPIDSHA1 = "" - deviceCopy.IFA = "" - deviceCopy.MACMD5 = "" - deviceCopy.MACSHA1 = "" +func scrubGEO(reqWrapper *openrtb_ext.RequestWrapper) { + //round user's geographic location by rounding off IP address and lat/lng data. + //this applies to both device.geo and user.geo + if reqWrapper.User != nil && reqWrapper.User.Geo != nil { + reqWrapper.User.Geo = scrubGeoPrecision(reqWrapper.User.Geo) } - switch ipv4 { - case ScrubStrategyIPV4Subnet: - deviceCopy.IP = scrubIP(device.IP, s.ipV4.AnonKeepBits, iputil.IPv4BitSize) + if reqWrapper.Device != nil && reqWrapper.Device.Geo != nil { + reqWrapper.Device.Geo = scrubGeoPrecision(reqWrapper.Device.Geo) } +} - switch ipv6 { - case ScrubStrategyIPV6Subnet: - deviceCopy.IPv6 = scrubIP(device.IPv6, s.ipV6.AnonKeepBits, iputil.IPv6BitSize) +func scrubGeoFull(reqWrapper *openrtb_ext.RequestWrapper) { + if reqWrapper.User != nil && reqWrapper.User.Geo != nil { + reqWrapper.User.Geo = &openrtb2.Geo{} } - - switch geo { - case ScrubStrategyGeoFull: - deviceCopy.Geo = scrubGeoFull(device.Geo) - case ScrubStrategyGeoReducedPrecision: - deviceCopy.Geo = scrubGeoPrecision(device.Geo) + if reqWrapper.Device != nil && reqWrapper.Device.Geo != nil { + reqWrapper.Device.Geo = &openrtb2.Geo{} } - return &deviceCopy } -func (scrubber) ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb2.User { - if user == nil { - return nil +func scrubDeviceIP(reqWrapper *openrtb_ext.RequestWrapper, ipConf IPConf) { + if reqWrapper.Device != nil { + reqWrapper.Device.IP = scrubIP(reqWrapper.Device.IP, ipConf.IPV4.AnonKeepBits, iputil.IPv4BitSize) + reqWrapper.Device.IPv6 = scrubIP(reqWrapper.Device.IPv6, ipConf.IPV6.AnonKeepBits, iputil.IPv6BitSize) } +} - userCopy := *user +func ScrubDeviceIDsIPsUserDemoExt(reqWrapper *openrtb_ext.RequestWrapper, ipConf IPConf, fieldName string, scrubFullGeo bool) { + scrubDeviceIDs(reqWrapper) + scrubDeviceIP(reqWrapper, ipConf) + scrubUserDemographics(reqWrapper) + scrubUserExt(reqWrapper, fieldName) - if strategy == ScrubStrategyUserIDAndDemographic { - userCopy.BuyerUID = "" - userCopy.ID = "" - userCopy.Ext = scrubExtIDs(userCopy.Ext, "eids") - userCopy.Yob = 0 - userCopy.Gender = "" + if scrubFullGeo { + scrubGeoFull(reqWrapper) + } else { + scrubGEO(reqWrapper) } +} - switch geo { - case ScrubStrategyGeoFull: - userCopy.Geo = scrubGeoFull(user.Geo) - case ScrubStrategyGeoReducedPrecision: - userCopy.Geo = scrubGeoPrecision(user.Geo) - } +func ScrubUserFPD(reqWrapper *openrtb_ext.RequestWrapper) { + scrubDeviceIDs(reqWrapper) + scrubUserIDs(reqWrapper) + scrubUserExt(reqWrapper, "data") + reqWrapper.User.EIDs = nil +} - return &userCopy +func ScrubGdprID(reqWrapper *openrtb_ext.RequestWrapper) { + scrubDeviceIDs(reqWrapper) + scrubUserDemographics(reqWrapper) + scrubUserExt(reqWrapper, "eids") +} + +func ScrubGeoAndDeviceIP(reqWrapper *openrtb_ext.RequestWrapper, ipConf IPConf) { + scrubDeviceIP(reqWrapper, ipConf) + scrubGEO(reqWrapper) } func scrubIP(ip string, ones, bits int) string { @@ -259,22 +155,25 @@ func scrubIP(ip string, ones, bits int) string { return ipMasked.String() } -func scrubGeoFull(geo *openrtb2.Geo) *openrtb2.Geo { +func scrubGeoPrecision(geo *openrtb2.Geo) *openrtb2.Geo { if geo == nil { return nil } - return &openrtb2.Geo{} -} + geoCopy := *geo -func scrubGeoPrecision(geo *openrtb2.Geo) *openrtb2.Geo { - if geo == nil { - return nil + if geoCopy.Lat != nil { + lat := *geo.Lat + lat = float64(int(lat*100.0+0.5)) / 100.0 + geoCopy.Lat = &lat + } + + if geoCopy.Lon != nil { + lon := *geo.Lon + lon = float64(int(lon*100.0+0.5)) / 100.0 + geoCopy.Lon = &lon } - geoCopy := *geo - geoCopy.Lat = float64(int(geo.Lat*100.0+0.5)) / 100.0 // Round Latitude - geoCopy.Lon = float64(int(geo.Lon*100.0+0.5)) / 100.0 // Round Longitude return &geoCopy } diff --git a/privacy/scrubber_test.go b/privacy/scrubber_test.go index 59e593fc167..ccb065bccbc 100644 --- a/privacy/scrubber_test.go +++ b/privacy/scrubber_test.go @@ -2,454 +2,350 @@ package privacy import ( "encoding/json" - "github.com/prebid/prebid-server/config" "testing" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/ptrutil" "github.com/stretchr/testify/assert" ) -func TestScrubDevice(t *testing.T) { - device := getTestDevice() - +func TestScrubDeviceIDs(t *testing.T) { testCases := []struct { - description string - expected *openrtb2.Device - id ScrubStrategyDeviceID - ipv4 ScrubStrategyIPV4 - ipv6 ScrubStrategyIPV6 - geo ScrubStrategyGeo + name string + deviceIn *openrtb2.Device + expectedDevice *openrtb2.Device }{ { - description: "All Strategies - None", - expected: device, - id: ScrubStrategyDeviceIDNone, - ipv4: ScrubStrategyIPV4None, - ipv6: ScrubStrategyIPV6None, - geo: ScrubStrategyGeoNone, - }, - { - description: "All Strategies - Strictest", - expected: &openrtb2.Device{ - DIDMD5: "", - DIDSHA1: "", - DPIDMD5: "", - DPIDSHA1: "", - MACSHA1: "", - MACMD5: "", - IFA: "", - IP: "1.2.3.0", - IPv6: "2001:1db8:2233:4400::", - Geo: &openrtb2.Geo{}, - }, - id: ScrubStrategyDeviceIDAll, - ipv4: ScrubStrategyIPV4Subnet, - ipv6: ScrubStrategyIPV6Subnet, - geo: ScrubStrategyGeoFull, - }, - { - description: "Isolated - ID - All", - expected: &openrtb2.Device{ - DIDMD5: "", - DIDSHA1: "", - DPIDMD5: "", - DPIDSHA1: "", - MACSHA1: "", - MACMD5: "", - IFA: "", - IP: "1.2.3.4", - IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", - Geo: device.Geo, - }, - id: ScrubStrategyDeviceIDAll, - ipv4: ScrubStrategyIPV4None, - ipv6: ScrubStrategyIPV6None, - geo: ScrubStrategyGeoNone, - }, - { - description: "Isolated - IPv4 - Lowest 8", - expected: &openrtb2.Device{ - DIDMD5: "anyDIDMD5", - DIDSHA1: "anyDIDSHA1", - DPIDMD5: "anyDPIDMD5", - DPIDSHA1: "anyDPIDSHA1", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IFA: "anyIFA", - IP: "1.2.3.0", - IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", - Geo: device.Geo, - }, - id: ScrubStrategyDeviceIDNone, - ipv4: ScrubStrategyIPV4Subnet, - ipv6: ScrubStrategyIPV6None, - geo: ScrubStrategyGeoNone, - }, - { - description: "Isolated - IPv6", - expected: &openrtb2.Device{ - DIDMD5: "anyDIDMD5", - DIDSHA1: "anyDIDSHA1", - DPIDMD5: "anyDPIDMD5", - DPIDSHA1: "anyDPIDSHA1", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IFA: "anyIFA", - IP: "1.2.3.4", - IPv6: "2001:1db8:2233:4400::", - Geo: device.Geo, - }, - id: ScrubStrategyDeviceIDNone, - ipv4: ScrubStrategyIPV4None, - ipv6: ScrubStrategyIPV6Subnet, - geo: ScrubStrategyGeoNone, - }, - { - description: "Isolated - Geo - Reduced Precision", - expected: &openrtb2.Device{ - DIDMD5: "anyDIDMD5", - DIDSHA1: "anyDIDSHA1", - DPIDMD5: "anyDPIDMD5", - DPIDSHA1: "anyDPIDSHA1", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IFA: "anyIFA", - IP: "1.2.3.4", - IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", - Geo: &openrtb2.Geo{ - Lat: 123.46, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - }, - id: ScrubStrategyDeviceIDNone, - ipv4: ScrubStrategyIPV4None, - ipv6: ScrubStrategyIPV6None, - geo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "Isolated - Geo - Full", - expected: &openrtb2.Device{ - DIDMD5: "anyDIDMD5", - DIDSHA1: "anyDIDSHA1", - DPIDMD5: "anyDPIDMD5", - DPIDSHA1: "anyDPIDSHA1", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IFA: "anyIFA", - IP: "1.2.3.4", - IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", - Geo: &openrtb2.Geo{}, - }, - id: ScrubStrategyDeviceIDNone, - ipv4: ScrubStrategyIPV4None, - ipv6: ScrubStrategyIPV6None, - geo: ScrubStrategyGeoFull, + name: "all", + deviceIn: &openrtb2.Device{DIDMD5: "MD5", DIDSHA1: "SHA1", DPIDMD5: "MD5", DPIDSHA1: "SHA1", IFA: "IFA", MACMD5: "MD5", MACSHA1: "SHA1"}, + expectedDevice: &openrtb2.Device{DIDMD5: "", DIDSHA1: "", DPIDMD5: "", DPIDSHA1: "", IFA: "", MACMD5: "", MACSHA1: ""}, + }, + { + name: "nil", + deviceIn: nil, + expectedDevice: nil, }, } - testIPMasking := getTestIPMasking() for _, test := range testCases { - result := NewScrubber(testIPMasking.IPv6Config, testIPMasking.IPv4Config).ScrubDevice(device, test.id, test.ipv4, test.ipv6, test.geo) - assert.Equal(t, test.expected, result, test.description) + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Device: test.deviceIn}} + scrubDeviceIDs(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedDevice, brw.Device) + }) } } -func TestScrubDeviceNil(t *testing.T) { - testIPMasking := getTestIPMasking() - result := NewScrubber(testIPMasking.IPv6Config, testIPMasking.IPv4Config).ScrubDevice(nil, ScrubStrategyDeviceIDNone, ScrubStrategyIPV4None, ScrubStrategyIPV6None, ScrubStrategyGeoNone) - assert.Nil(t, result) +func TestScrubUserIDs(t *testing.T) { + testCases := []struct { + name string + userIn *openrtb2.User + expectedUser *openrtb2.User + }{ + { + name: "all", + userIn: &openrtb2.User{Data: []openrtb2.Data{}, ID: "ID", BuyerUID: "bID", Yob: 2000, Gender: "M", Keywords: "keywords", KwArray: nil}, + expectedUser: &openrtb2.User{Data: nil, ID: "", BuyerUID: "", Yob: 0, Gender: "", Keywords: "", KwArray: nil}, + }, + { + name: "nil", + userIn: nil, + expectedUser: nil, + }, + } + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{User: test.userIn}} + scrubUserIDs(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedUser, brw.User) + }) + } } -func TestScrubUser(t *testing.T) { - user := getTestUser() - +func TestScrubUserDemographics(t *testing.T) { testCases := []struct { - description string - expected *openrtb2.User - scrubUser ScrubStrategyUser - scrubGeo ScrubStrategyGeo + name string + userIn *openrtb2.User + expectedUser *openrtb2.User }{ { - description: "User ID And Demographic & Geo Full", - expected: &openrtb2.User{ - ID: "", - BuyerUID: "", - Yob: 0, - Gender: "", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{}, - }, - scrubUser: ScrubStrategyUserIDAndDemographic, - scrubGeo: ScrubStrategyGeoFull, - }, - { - description: "User ID And Demographic & Geo Reduced", - expected: &openrtb2.User{ - ID: "", - BuyerUID: "", - Yob: 0, - Gender: "", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{ - Lat: 123.46, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - }, - scrubUser: ScrubStrategyUserIDAndDemographic, - scrubGeo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "User ID And Demographic & Geo None", - expected: &openrtb2.User{ - ID: "", - BuyerUID: "", - Yob: 0, - Gender: "", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{ - Lat: 123.456, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - }, - scrubUser: ScrubStrategyUserIDAndDemographic, - scrubGeo: ScrubStrategyGeoNone, - }, - { - description: "User None & Geo Full", - expected: &openrtb2.User{ - ID: "anyID", - BuyerUID: "anyBuyerUID", - Yob: 42, - Gender: "anyGender", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{}, - }, - scrubUser: ScrubStrategyUserNone, - scrubGeo: ScrubStrategyGeoFull, - }, - { - description: "User None & Geo Reduced", - expected: &openrtb2.User{ - ID: "anyID", - BuyerUID: "anyBuyerUID", - Yob: 42, - Gender: "anyGender", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{ - Lat: 123.46, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - }, - scrubUser: ScrubStrategyUserNone, - scrubGeo: ScrubStrategyGeoReducedPrecision, - }, - { - description: "User None & Geo None", - expected: &openrtb2.User{ - ID: "anyID", - BuyerUID: "anyBuyerUID", - Yob: 42, - Gender: "anyGender", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{ - Lat: 123.456, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - }, - scrubUser: ScrubStrategyUserNone, - scrubGeo: ScrubStrategyGeoNone, + name: "all", + userIn: &openrtb2.User{ID: "ID", BuyerUID: "bID", Yob: 2000, Gender: "M"}, + expectedUser: &openrtb2.User{ID: "", BuyerUID: "", Yob: 0, Gender: ""}, + }, + { + name: "nil", + userIn: nil, + expectedUser: nil, }, } - - testIPMasking := getTestIPMasking() for _, test := range testCases { - result := NewScrubber(testIPMasking.IPv6Config, testIPMasking.IPv4Config).ScrubUser(user, test.scrubUser, test.scrubGeo) - assert.Equal(t, test.expected, result, test.description) + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{User: test.userIn}} + scrubUserDemographics(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedUser, brw.User) + }) } } -func TestScrubUserNil(t *testing.T) { - testIPMasking := getTestIPMasking() - result := NewScrubber(testIPMasking.IPv6Config, testIPMasking.IPv4Config).ScrubUser(nil, ScrubStrategyUserNone, ScrubStrategyGeoNone) - assert.Nil(t, result) +func TestScrubUserExt(t *testing.T) { + testCases := []struct { + name string + userIn *openrtb2.User + fieldName string + expectedUser *openrtb2.User + }{ + { + name: "nil_user", + userIn: nil, + expectedUser: nil, + }, + { + name: "nil_ext", + userIn: &openrtb2.User{ID: "ID", Ext: nil}, + expectedUser: &openrtb2.User{ID: "ID", Ext: nil}, + }, + { + name: "empty_ext", + userIn: &openrtb2.User{ID: "ID", Ext: json.RawMessage(`{}`)}, + expectedUser: &openrtb2.User{ID: "ID", Ext: json.RawMessage(`{}`)}, + }, + { + name: "ext_with_field", + userIn: &openrtb2.User{ID: "ID", Ext: json.RawMessage(`{"data":"123","test":1}`)}, + fieldName: "data", + expectedUser: &openrtb2.User{ID: "ID", Ext: json.RawMessage(`{"test":1}`)}, + }, + { + name: "ext_without_field", + userIn: &openrtb2.User{ID: "ID", Ext: json.RawMessage(`{"data":"123","test":1}`)}, + fieldName: "noData", + expectedUser: &openrtb2.User{ID: "ID", Ext: json.RawMessage(`{"data":"123","test":1}`)}, + }, + { + name: "nil", + userIn: nil, + expectedUser: nil, + }, + } + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{User: test.userIn}} + scrubUserExt(brw, test.fieldName) + brw.RebuildRequest() + assert.Equal(t, test.expectedUser, brw.User) + }) + } } -func TestScrubRequest(t *testing.T) { - - imps := []openrtb2.Imp{ - {ID: "testId", Ext: json.RawMessage(`{"test": 1, "tid": 2}`)}, +func TestScrubEids(t *testing.T) { + testCases := []struct { + name string + userIn *openrtb2.User + expectedUser *openrtb2.User + }{ + { + name: "eids", + userIn: &openrtb2.User{ID: "ID", EIDs: []openrtb2.EID{}}, + expectedUser: &openrtb2.User{ID: "ID", EIDs: nil}, + }, + { + name: "nil_eids", + userIn: &openrtb2.User{ID: "ID", EIDs: nil}, + expectedUser: &openrtb2.User{ID: "ID", EIDs: nil}, + }, + { + name: "nil", + userIn: nil, + expectedUser: nil, + }, } - source := &openrtb2.Source{ - TID: "testTid", + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{User: test.userIn}} + ScrubEIDs(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedUser, brw.User) + }) } - device := getTestDevice() - user := getTestUser() - user.Ext = json.RawMessage(`{"data": 1, "eids": 2}`) - user.EIDs = []openrtb2.EID{{Source: "test"}} +} +func TestScrubTID(t *testing.T) { testCases := []struct { - description string - enforcement Enforcement - userExtPresent bool - expected *openrtb2.BidRequest + name string + sourceIn *openrtb2.Source + impIn []openrtb2.Imp + expectedSource *openrtb2.Source + expectedImp []openrtb2.Imp }{ { - description: "enforce transmitUFPD with user.ext", - enforcement: Enforcement{UFPD: true}, - userExtPresent: true, - expected: &openrtb2.BidRequest{ - Imp: imps, - Source: source, - User: &openrtb2.User{ - EIDs: []openrtb2.EID{{Source: "test"}}, - Geo: user.Geo, - Ext: json.RawMessage(`{"eids":2}`), - }, - Device: &openrtb2.Device{ - IP: "1.2.3.4", - IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", - Geo: device.Geo, - }, - }, - }, - { - description: "enforce transmitUFPD without user.ext", - enforcement: Enforcement{UFPD: true}, - userExtPresent: false, - expected: &openrtb2.BidRequest{ - Imp: imps, - Source: source, - User: &openrtb2.User{ - EIDs: []openrtb2.EID{{Source: "test"}}, - Geo: user.Geo, - }, - Device: &openrtb2.Device{ - IP: "1.2.3.4", - IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", - Geo: device.Geo, - }, - }, - }, - { - description: "enforce transmitEids", - enforcement: Enforcement{Eids: true}, - userExtPresent: true, - expected: &openrtb2.BidRequest{ - Imp: imps, - Source: source, - Device: device, - User: &openrtb2.User{ - ID: "anyID", - BuyerUID: "anyBuyerUID", - Yob: 42, - Gender: "anyGender", - Geo: user.Geo, - EIDs: nil, - Ext: json.RawMessage(`{"data":1}`), - }, - }, - }, - { - description: "enforce transmitTid", - enforcement: Enforcement{TID: true}, - userExtPresent: true, - expected: &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{ - {ID: "testId", Ext: json.RawMessage(`{"test":1}`)}, - }, - Source: &openrtb2.Source{ - TID: "", - }, - Device: device, - User: &openrtb2.User{ - ID: "anyID", - BuyerUID: "anyBuyerUID", - Yob: 42, - Gender: "anyGender", - Geo: user.Geo, - EIDs: []openrtb2.EID{{Source: "test"}}, - Ext: json.RawMessage(`{"data": 1, "eids": 2}`), - }, - }, - }, - { - description: "enforce precise Geo", - enforcement: Enforcement{PreciseGeo: true}, - userExtPresent: true, - expected: &openrtb2.BidRequest{ - Imp: imps, - Source: source, - User: &openrtb2.User{ - ID: "anyID", - BuyerUID: "anyBuyerUID", - Yob: 42, - Gender: "anyGender", - Geo: &openrtb2.Geo{ - Lat: 123.46, Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - EIDs: []openrtb2.EID{{Source: "test"}}, - Ext: json.RawMessage(`{"data": 1, "eids": 2}`), - }, - Device: &openrtb2.Device{ - IFA: "anyIFA", - DIDSHA1: "anyDIDSHA1", - DIDMD5: "anyDIDMD5", - DPIDSHA1: "anyDPIDSHA1", - DPIDMD5: "anyDPIDMD5", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IP: "1.2.3.0", - IPv6: "2001:1db8:2233:4400::", - Geo: &openrtb2.Geo{ - Lat: 123.46, Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - }, - }, + name: "nil", + sourceIn: nil, + expectedSource: nil, }, + { + name: "nil_imp_ext", + sourceIn: &openrtb2.Source{TID: "tid"}, + impIn: []openrtb2.Imp{{ID: "impID", Ext: nil}}, + expectedSource: &openrtb2.Source{TID: ""}, + expectedImp: []openrtb2.Imp{{ID: "impID", Ext: nil}}, + }, + { + name: "empty_imp_ext", + sourceIn: &openrtb2.Source{TID: "tid"}, + impIn: []openrtb2.Imp{{ID: "impID", Ext: json.RawMessage(`{}`)}}, + expectedSource: &openrtb2.Source{TID: ""}, + expectedImp: []openrtb2.Imp{{ID: "impID", Ext: json.RawMessage(`{}`)}}, + }, + { + name: "ext_with_tid", + sourceIn: &openrtb2.Source{TID: "tid"}, + impIn: []openrtb2.Imp{{ID: "impID", Ext: json.RawMessage(`{"tid":"123","test":1}`)}}, + expectedSource: &openrtb2.Source{TID: ""}, + expectedImp: []openrtb2.Imp{{ID: "impID", Ext: json.RawMessage(`{"test":1}`)}}, + }, + { + name: "ext_without_tid", + sourceIn: &openrtb2.Source{TID: "tid"}, + impIn: []openrtb2.Imp{{ID: "impID", Ext: json.RawMessage(`{"data":"123","test":1}`)}}, + expectedSource: &openrtb2.Source{TID: ""}, + expectedImp: []openrtb2.Imp{{ID: "impID", Ext: json.RawMessage(`{"data":"123","test":1}`)}}, + }, + } + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Source: test.sourceIn, Imp: test.impIn}} + ScrubTID(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedSource, brw.Source) + assert.Equal(t, test.expectedImp, brw.Imp) + }) } +} - testIPMasking := getTestIPMasking() +func TestScrubGEO(t *testing.T) { + testCases := []struct { + name string + userIn *openrtb2.User + expectedUser *openrtb2.User + deviceIn *openrtb2.Device + expectedDevice *openrtb2.Device + }{ + { + name: "nil", + userIn: nil, + expectedUser: nil, + deviceIn: nil, + expectedDevice: nil, + }, + { + name: "nil_user_geo", + userIn: &openrtb2.User{ID: "ID", Geo: nil}, + expectedUser: &openrtb2.User{ID: "ID", Geo: nil}, + deviceIn: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.123)}}, + expectedDevice: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.12)}}, + }, + { + name: "with_user_geo", + userIn: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.123)}}, + expectedUser: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.12)}}, + deviceIn: &openrtb2.Device{}, + expectedDevice: &openrtb2.Device{}, + }, + { + name: "nil_device_geo", + userIn: &openrtb2.User{}, + expectedUser: &openrtb2.User{}, + deviceIn: &openrtb2.Device{Geo: nil}, + expectedDevice: &openrtb2.Device{Geo: nil}, + }, + { + name: "with_device_geo", + userIn: &openrtb2.User{}, + expectedUser: &openrtb2.User{}, + deviceIn: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.123)}}, + expectedDevice: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.12)}}, + }, + { + name: "with_user_and_device_geo", + userIn: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.123)}}, + expectedUser: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.12)}}, + deviceIn: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.123)}}, + expectedDevice: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.12)}}, + }, + } for _, test := range testCases { - t.Run(test.description, func(t *testing.T) { - bidRequest := &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{ - {ID: "testId", Ext: json.RawMessage(`{"test": 1, "tid": 2}`)}, - }, - Source: &openrtb2.Source{ - TID: "testTid", - }, - User: getTestUser(), - Device: getTestDevice(), - } - if test.userExtPresent { - bidRequest.User.Ext = json.RawMessage(`{"data": 1, "eids": 2}`) - } else { - bidRequest.User.Ext = nil - } - bidRequest.User.EIDs = []openrtb2.EID{{Source: "test"}} + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{User: test.userIn, Device: test.deviceIn}} + scrubGEO(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedUser, brw.User) + assert.Equal(t, test.expectedDevice, brw.Device) + }) + } +} - result := NewScrubber(testIPMasking.IPv6Config, testIPMasking.IPv4Config).ScrubRequest(bidRequest, test.enforcement) - assert.Equal(t, test.expected, result, test.description) +func TestScrubGeoFull(t *testing.T) { + testCases := []struct { + name string + userIn *openrtb2.User + expectedUser *openrtb2.User + deviceIn *openrtb2.Device + expectedDevice *openrtb2.Device + }{ + { + name: "nil", + userIn: nil, + expectedUser: nil, + deviceIn: nil, + expectedDevice: nil, + }, + { + name: "nil_user_geo", + userIn: &openrtb2.User{ID: "ID", Geo: nil}, + expectedUser: &openrtb2.User{ID: "ID", Geo: nil}, + deviceIn: &openrtb2.Device{}, + expectedDevice: &openrtb2.Device{}, + }, + { + name: "with_user_geo", + userIn: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.123)}}, + expectedUser: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{}}, + deviceIn: &openrtb2.Device{}, + expectedDevice: &openrtb2.Device{}, + }, + { + name: "nil_device_geo", + userIn: &openrtb2.User{}, + expectedUser: &openrtb2.User{}, + deviceIn: &openrtb2.Device{Geo: nil}, + expectedDevice: &openrtb2.Device{Geo: nil}, + }, + { + name: "with_device_geo", + userIn: &openrtb2.User{}, + expectedUser: &openrtb2.User{}, + deviceIn: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.123)}}, + expectedDevice: &openrtb2.Device{Geo: &openrtb2.Geo{}}, + }, + { + name: "with_user_and_device_geo", + userIn: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.123)}}, + expectedUser: &openrtb2.User{ID: "ID", Geo: &openrtb2.Geo{}}, + deviceIn: &openrtb2.Device{Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.123)}}, + expectedDevice: &openrtb2.Device{Geo: &openrtb2.Geo{}}, + }, + } + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + brw := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{User: test.userIn, Device: test.deviceIn}} + scrubGeoFull(brw) + brw.RebuildRequest() + assert.Equal(t, test.expectedUser, brw.User) + assert.Equal(t, test.expectedDevice, brw.Device) }) } } @@ -509,42 +405,6 @@ func TestScrubIP(t *testing.T) { bits: 128, maskBits: 96, }, - { - IP: "2001:1db8:0000:0000:0000:ff00:0042:8329", - cleanedIP: "2001:1db8::ff00:42:0", - bits: 128, - maskBits: 112, - }, - { - IP: "2001:1db8:0000:0000:0000:ff00:0042:0", - cleanedIP: "2001:1db8::ff00:42:0", - bits: 128, - maskBits: 112, - }, - { - IP: "127.0.0.1", - cleanedIP: "127.0.0.0", - bits: 32, - maskBits: 24, - }, - { - IP: "0.0.0.0", - cleanedIP: "0.0.0.0", - bits: 32, - maskBits: 24, - }, - { - IP: "192.127.111.134", - cleanedIP: "192.127.111.0", - bits: 32, - maskBits: 24, - }, - { - IP: "192.127.111.0", - cleanedIP: "192.127.111.0", - bits: 32, - maskBits: 24, - }, } for _, test := range testCases { t.Run(test.IP, func(t *testing.T) { @@ -555,43 +415,17 @@ func TestScrubIP(t *testing.T) { } } -func TestScrubGeoFull(t *testing.T) { - geo := &openrtb2.Geo{ - Lat: 123.456, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - } - geoExpected := &openrtb2.Geo{ - Lat: 0, - Lon: 0, - Metro: "", - City: "", - ZIP: "", - } - - result := scrubGeoFull(geo) - - assert.Equal(t, geoExpected, result) -} - -func TestScrubGeoFullWhenNil(t *testing.T) { - result := scrubGeoFull(nil) - assert.Nil(t, result) -} - func TestScrubGeoPrecision(t *testing.T) { geo := &openrtb2.Geo{ - Lat: 123.456, - Lon: 678.89, + Lat: ptrutil.ToPtr(123.456), + Lon: ptrutil.ToPtr(678.89), Metro: "some metro", City: "some city", ZIP: "some zip", } geoExpected := &openrtb2.Geo{ - Lat: 123.46, - Lon: 678.89, + Lat: ptrutil.ToPtr(123.46), + Lon: ptrutil.ToPtr(678.89), Metro: "some metro", City: "some city", ZIP: "some zip", @@ -653,26 +487,11 @@ func TestScrubUserExtIDs(t *testing.T) { userExt: json.RawMessage(`{"anyExisting":{"existing":42},"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), expected: json.RawMessage(`{"anyExisting":{"existing":42}}`), }, - { - description: "Remove eids Only", - userExt: json.RawMessage(`{"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), - expected: json.RawMessage(`{}`), - }, { description: "Remove eids Only - Empty Array", userExt: json.RawMessage(`{"eids":[]}`), expected: json.RawMessage(`{}`), }, - { - description: "Remove eids Only - With Other Data", - userExt: json.RawMessage(`{"anyExisting":42,"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), - expected: json.RawMessage(`{"anyExisting":42}`), - }, - { - description: "Remove eids Only - With Other Nested Data", - userExt: json.RawMessage(`{"anyExisting":{"existing":42},"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), - expected: json.RawMessage(`{"anyExisting":{"existing":42}}`), - }, } for _, test := range testCases { @@ -680,52 +499,3 @@ func TestScrubUserExtIDs(t *testing.T) { assert.Equal(t, test.expected, result, test.description) } } - -func getTestUser() *openrtb2.User { - return &openrtb2.User{ - ID: "anyID", - BuyerUID: "anyBuyerUID", - Yob: 42, - Gender: "anyGender", - Ext: json.RawMessage(`{}`), - Geo: &openrtb2.Geo{ - Lat: 123.456, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - } -} - -func getTestDevice() *openrtb2.Device { - return &openrtb2.Device{ - DIDMD5: "anyDIDMD5", - DIDSHA1: "anyDIDSHA1", - DPIDMD5: "anyDPIDMD5", - DPIDSHA1: "anyDPIDSHA1", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IFA: "anyIFA", - IP: "1.2.3.4", - IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", - Geo: &openrtb2.Geo{ - Lat: 123.456, - Lon: 678.89, - Metro: "some metro", - City: "some city", - ZIP: "some zip", - }, - } -} - -func getTestIPMasking() config.AccountPrivacy { - return config.AccountPrivacy{ - IPv6Config: config.IPv6{ - 54, - }, - IPv4Config: config.IPv4{ - 24, - }, - } -} diff --git a/privacy/writer.go b/privacy/writer.go index 88b7c6ee5d6..1f4a13cd6a4 100644 --- a/privacy/writer.go +++ b/privacy/writer.go @@ -1,6 +1,6 @@ package privacy -import "github.com/prebid/openrtb/v19/openrtb2" +import "github.com/prebid/openrtb/v20/openrtb2" // PolicyWriter mutates an OpenRTB bid request with a policy's regulatory information. type PolicyWriter interface { diff --git a/privacy/writer_test.go b/privacy/writer_test.go index 1ee94abaf22..47b4365d6c0 100644 --- a/privacy/writer_test.go +++ b/privacy/writer_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" "github.com/stretchr/testify/assert" ) diff --git a/privacysandbox/topics.go b/privacysandbox/topics.go new file mode 100644 index 00000000000..3ed620b4bdd --- /dev/null +++ b/privacysandbox/topics.go @@ -0,0 +1,228 @@ +package privacysandbox + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +type Topic struct { + SegTax int `json:"segtax,omitempty"` + SegClass string `json:"segclass,omitempty"` + SegIDs []int `json:"segids,omitempty"` +} + +// ParseTopicsFromHeader parses the Sec-Browsing-Topics header data into Topics object +func ParseTopicsFromHeader(secBrowsingTopics string) ([]Topic, []error) { + topics := make([]Topic, 0, 10) + var warnings []error + + for _, field := range strings.Split(secBrowsingTopics, ",") { + field = strings.TrimSpace(field) + if field == "" || strings.HasPrefix(field, "();p=") { + continue + } + + if len(topics) < 10 { + if topic, ok := parseTopicSegment(field); ok { + topics = append(topics, topic) + } else { + warnings = append(warnings, formatWarning(field)) + } + } else { + warnings = append(warnings, formatWarning(field+" discarded due to limit reached.")) + } + } + + return topics, warnings +} + +// parseTopicSegment parses a single topic segment from the header into Topics object +func parseTopicSegment(field string) (Topic, bool) { + segment := strings.Split(field, ";") + if len(segment) != 2 { + return Topic{}, false + } + + segmentsIDs := strings.TrimSpace(segment[0]) + if len(segmentsIDs) < 3 || segmentsIDs[0] != '(' || segmentsIDs[len(segmentsIDs)-1] != ')' { + return Topic{}, false + } + + segtax, segclass := parseSegTaxSegClass(segment[1]) + if segtax == 0 || segclass == "" { + return Topic{}, false + } + + segIDs, err := parseSegmentIDs(segmentsIDs[1 : len(segmentsIDs)-1]) + if err != nil { + return Topic{}, false + } + + return Topic{ + SegTax: segtax, + SegClass: segclass, + SegIDs: segIDs, + }, true +} + +func parseSegTaxSegClass(seg string) (int, string) { + taxanomyModel := strings.Split(seg, ":") + if len(taxanomyModel) != 3 { + return 0, "" + } + + // taxanomyModel[0] is v=browser_version, we don't need it + taxanomyVer := strings.TrimSpace(taxanomyModel[1]) + taxanomy, err := strconv.Atoi(taxanomyVer) + if err != nil || taxanomy < 1 || taxanomy > 10 { + return 0, "" + } + + segtax := 600 + (taxanomy - 1) + segclass := strings.TrimSpace(taxanomyModel[2]) + return segtax, segclass +} + +// parseSegmentIDs parses the segment ids from the header string into int array +func parseSegmentIDs(segmentsIDs string) ([]int, error) { + var selectedSegmentIDs []int + for _, segmentID := range strings.Fields(segmentsIDs) { + segmentID = strings.TrimSpace(segmentID) + selectedSegmentID, err := strconv.Atoi(segmentID) + if err != nil || selectedSegmentID <= 0 { + return selectedSegmentIDs, errors.New("invalid segment id") + } + selectedSegmentIDs = append(selectedSegmentIDs, selectedSegmentID) + } + + return selectedSegmentIDs, nil +} + +func UpdateUserDataWithTopics(userData []openrtb2.Data, headerData []Topic, topicsDomain string) []openrtb2.Data { + if topicsDomain == "" { + return userData + } + + // headerDataMap groups segIDs by segtax and segclass for faster lookup and tracking of new segIDs yet to be added to user.data + // tracking is done by removing segIDs from segIDsMap once they are added to user.data, ensuring that headerDataMap will always have unique segtax-segclass-segIDs + // the only drawback of tracking via deleting segtax-segclass from headerDataMap is that this would not track duplicate entries within user.data which is fine because we are only merging header data with the provided user.data + headerDataMap := createHeaderDataMap(headerData) + + for i, data := range userData { + ext := &Topic{} + err := json.Unmarshal(data.Ext, ext) + if err != nil { + continue + } + + if ext.SegTax == 0 || ext.SegClass == "" { + continue + } + + if newSegIDs := findNewSegIDs(data.Name, topicsDomain, *ext, data.Segment, headerDataMap); newSegIDs != nil { + for _, segID := range newSegIDs { + userData[i].Segment = append(userData[i].Segment, openrtb2.Segment{ID: strconv.Itoa(segID)}) + } + + delete(headerDataMap[ext.SegTax], ext.SegClass) + } + } + + for segTax, segClassMap := range headerDataMap { + for segClass, segIDs := range segClassMap { + if len(segIDs) != 0 { + data := openrtb2.Data{ + Name: topicsDomain, + } + + var err error + data.Ext, err = jsonutil.Marshal(Topic{SegTax: segTax, SegClass: segClass}) + if err != nil { + continue + } + + for segID := range segIDs { + data.Segment = append(data.Segment, openrtb2.Segment{ + ID: strconv.Itoa(segID), + }) + } + + userData = append(userData, data) + } + } + } + + return userData +} + +// createHeaderDataMap creates a map of header data (segtax-segclass-segIDs) for faster lookup +// topicsdomain is not needed as we are only interested data from one domain configured in host config +func createHeaderDataMap(headerData []Topic) map[int]map[string]map[int]struct{} { + headerDataMap := make(map[int]map[string]map[int]struct{}) + + for _, topic := range headerData { + segClassMap, ok := headerDataMap[topic.SegTax] + if !ok { + segClassMap = make(map[string]map[int]struct{}) + headerDataMap[topic.SegTax] = segClassMap + } + + segIDsMap, ok := segClassMap[topic.SegClass] + if !ok { + segIDsMap = make(map[int]struct{}) + segClassMap[topic.SegClass] = segIDsMap + } + + for _, segID := range topic.SegIDs { + segIDsMap[segID] = struct{}{} + } + } + + return headerDataMap +} + +// findNewSegIDs merge unique segIDs in single user.data if request.user.data and header data match. i.e. segclass, segtax and topicsdomain match +func findNewSegIDs(dataName, topicsDomain string, userData Topic, userDataSegments []openrtb2.Segment, headerDataMap map[int]map[string]map[int]struct{}) []int { + if dataName != topicsDomain { + return nil + } + + segClassMap, exists := headerDataMap[userData.SegTax] + if !exists { + return nil + } + + segIDsMap, exists := segClassMap[userData.SegClass] + if !exists { + return nil + } + + // remove existing segIDs entries + for _, segID := range userDataSegments { + if id, err := strconv.Atoi(segID.ID); err == nil { + delete(segIDsMap, id) + } + } + + // collect remaining segIDs + segIDs := make([]int, 0, len(segIDsMap)) + for segID := range segIDsMap { + segIDs = append(segIDs, segID) + } + + return segIDs +} + +func formatWarning(msg string) error { + return &errortypes.DebugWarning{ + WarningCode: errortypes.SecBrowsingTopicsWarningCode, + Message: fmt.Sprintf("Invalid field in Sec-Browsing-Topics header: %s", msg), + } +} diff --git a/privacysandbox/topics_test.go b/privacysandbox/topics_test.go new file mode 100644 index 00000000000..73d5ef0c12f --- /dev/null +++ b/privacysandbox/topics_test.go @@ -0,0 +1,722 @@ +package privacysandbox + +import ( + "encoding/json" + "sort" + "testing" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/stretchr/testify/assert" +) + +func TestParseTopicsFromHeader(t *testing.T) { + type args struct { + secBrowsingTopics string + } + tests := []struct { + name string + args args + wantTopic []Topic + wantError []error + }{ + { + name: "empty header", + args: args{secBrowsingTopics: " "}, + wantTopic: []Topic{}, + wantError: nil, + }, + { + name: "invalid header value", + args: args{secBrowsingTopics: "some-sec-cookie-value"}, + wantTopic: []Topic{}, + wantError: []error{ + &errortypes.DebugWarning{ + Message: "Invalid field in Sec-Browsing-Topics header: some-sec-cookie-value", + WarningCode: errortypes.SecBrowsingTopicsWarningCode, + }, + }, + }, + { + name: "header with only finish padding", + args: args{secBrowsingTopics: "();p=P0000000000000000000000000000000"}, + wantTopic: []Topic{}, + wantError: nil, + }, + { + name: "header with one valid field", + args: args{secBrowsingTopics: "(1);v=chrome.1:1:2, ();p=P00000000000"}, + wantTopic: []Topic{ + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{1}, + }, + }, + wantError: nil, + }, + { + name: "header without finish padding", + args: args{secBrowsingTopics: "(1);v=chrome.1:1:2"}, + wantTopic: []Topic{ + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{1}, + }, + }, + wantError: nil, + }, + { + name: "header with more than 10 valid field, should return only 10", + args: args{secBrowsingTopics: "(1);v=chrome.1:1:2, (2);v=chrome.1:1:2, (3);v=chrome.1:1:2, (4);v=chrome.1:1:2, (5);v=chrome.1:1:2, (6);v=chrome.1:1:2, (7);v=chrome.1:1:2, (8);v=chrome.1:1:2, (9);v=chrome.1:1:2, (10);v=chrome.1:1:2, (11);v=chrome.1:1:2, (12);v=chrome.1:1:2, ();p=P00000000000"}, + wantTopic: []Topic{ + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{1}, + }, + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{2}, + }, + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{3}, + }, + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{4}, + }, + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{5}, + }, + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{6}, + }, + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{7}, + }, + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{8}, + }, + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{9}, + }, + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{10}, + }, + }, + wantError: []error{ + &errortypes.DebugWarning{ + Message: "Invalid field in Sec-Browsing-Topics header: (11);v=chrome.1:1:2 discarded due to limit reached.", + WarningCode: errortypes.SecBrowsingTopicsWarningCode, + }, + &errortypes.DebugWarning{ + Message: "Invalid field in Sec-Browsing-Topics header: (12);v=chrome.1:1:2 discarded due to limit reached.", + WarningCode: errortypes.SecBrowsingTopicsWarningCode, + }, + }, + }, + { + name: "header with one valid field having multiple segIDs", + args: args{secBrowsingTopics: "(1 2);v=chrome.1:1:2, ();p=P00000000000"}, + wantTopic: []Topic{ + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{1, 2}, + }, + }, + wantError: nil, + }, + { + name: "header with two valid fields having different taxonomies", + args: args{secBrowsingTopics: "(1);v=chrome.1:1:2, (1);v=chrome.1:2:2, ();p=P0000000000"}, + wantTopic: []Topic{ + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{1}, + }, + { + SegTax: 601, + SegClass: "2", + SegIDs: []int{1}, + }, + }, + wantError: nil, + }, + { + name: "header with one valid field and another invalid field (w/o segIDs), should return only one valid field", + args: args{secBrowsingTopics: "(1);v=chrome.1:2:3, ();v=chrome.1:2:3, ();p=P0000000000"}, + wantTopic: []Topic{ + { + SegTax: 601, + SegClass: "3", + SegIDs: []int{1}, + }, + }, + wantError: []error{ + &errortypes.DebugWarning{ + Message: "Invalid field in Sec-Browsing-Topics header: ();v=chrome.1:2:3", + WarningCode: errortypes.SecBrowsingTopicsWarningCode, + }, + }, + }, + { + name: "header with two valid fields having different model version", + args: args{secBrowsingTopics: "(1);v=chrome.1:2:3, (2);v=chrome.1:2:3, ();p=P0000000000"}, + wantTopic: []Topic{ + { + SegTax: 601, + SegClass: "3", + SegIDs: []int{1}, + }, + { + SegTax: 601, + SegClass: "3", + SegIDs: []int{2}, + }, + }, + wantError: nil, + }, + { + name: "header with one valid fields and two invalid fields (one with taxanomy < 0 and another with taxanomy > 10), should return only one valid field", + args: args{secBrowsingTopics: "(1);v=chrome.1:11:2, (1);v=chrome.1:5:6, (1);v=chrome.1:0:2, ();p=P0000000000"}, + wantTopic: []Topic{ + { + SegTax: 604, + SegClass: "6", + SegIDs: []int{1}, + }, + }, + wantError: []error{ + &errortypes.DebugWarning{ + Message: "Invalid field in Sec-Browsing-Topics header: (1);v=chrome.1:11:2", + WarningCode: errortypes.SecBrowsingTopicsWarningCode, + }, + &errortypes.DebugWarning{ + Message: "Invalid field in Sec-Browsing-Topics header: (1);v=chrome.1:0:2", + WarningCode: errortypes.SecBrowsingTopicsWarningCode, + }, + }, + }, + { + name: "header with with valid fields having special characters (whitespaces, etc)", + args: args{secBrowsingTopics: "(1 2 4 6 7 4567 ) ; v=chrome.1: 1 : 2, (1);v=chrome.1, ();p=P0000000000"}, + wantTopic: []Topic{ + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{1, 2, 4, 6, 7, 4567}, + }, + }, + wantError: []error{ + &errortypes.DebugWarning{ + Message: "Invalid field in Sec-Browsing-Topics header: (1);v=chrome.1", + WarningCode: errortypes.SecBrowsingTopicsWarningCode, + }, + }, + }, + { + name: "header with one valid field having a negative segId, drop field", + args: args{secBrowsingTopics: "(1 -3);v=chrome.1:1:2, ();p=P00000000000"}, + wantTopic: []Topic{}, + wantError: []error{ + &errortypes.DebugWarning{ + Message: "Invalid field in Sec-Browsing-Topics header: (1 -3);v=chrome.1:1:2", + WarningCode: errortypes.SecBrowsingTopicsWarningCode, + }, + }, + }, + { + name: "header with one valid field having a segId=0, drop field", + args: args{secBrowsingTopics: "(1 0);v=chrome.1:1:2, ();p=P00000000000"}, + wantTopic: []Topic{}, + wantError: []error{ + &errortypes.DebugWarning{ + Message: "Invalid field in Sec-Browsing-Topics header: (1 0);v=chrome.1:1:2", + WarningCode: errortypes.SecBrowsingTopicsWarningCode, + }, + }, + }, + { + name: "header with one valid field having a segId value more than MaxInt, drop field", + args: args{secBrowsingTopics: "(1 9223372036854775808);v=chrome.1:1:2, ();p=P00000000000"}, + wantTopic: []Topic{}, + wantError: []error{ + &errortypes.DebugWarning{ + Message: "Invalid field in Sec-Browsing-Topics header: (1 9223372036854775808);v=chrome.1:1:2", + WarningCode: errortypes.SecBrowsingTopicsWarningCode, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotTopic, gotError := ParseTopicsFromHeader(tt.args.secBrowsingTopics) + assert.Equal(t, tt.wantTopic, gotTopic) + assert.Equal(t, tt.wantError, gotError) + }) + } +} + +func TestUpdateUserDataWithTopics(t *testing.T) { + type args struct { + userData []openrtb2.Data + headerData []Topic + topicsDomain string + } + tests := []struct { + name string + args args + want []openrtb2.Data + }{ + { + name: "empty topics, empty user data, no change in user data", + args: args{ + userData: nil, + headerData: nil, + }, + want: nil, + }, + { + name: "empty topics, non-empty user data, no change in user data", + args: args{ + userData: []openrtb2.Data{ + { + ID: "1", + Name: "data1", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + }, + }, + }, + headerData: nil, + }, + want: []openrtb2.Data{ + { + ID: "1", + Name: "data1", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + }, + }, + }, + }, + { + name: "topicsDomain empty, no change in user data", + args: args{ + userData: []openrtb2.Data{ + { + ID: "1", + Name: "data1", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + }, + }, + }, + headerData: []Topic{ + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{1, 2}, + }, + }, + topicsDomain: "", + }, + want: []openrtb2.Data{ + { + ID: "1", + Name: "data1", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + }, + }, + }, + }, + { + name: "non-empty topics, empty user data, topics from header copied to user data", + args: args{ + userData: nil, + headerData: []Topic{ + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{1, 2}, + }, + }, + topicsDomain: "ads.pubmatic.com", + }, + want: []openrtb2.Data{ + { + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + }, + Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`), + }, + }, + }, + { + name: "non-empty topics, non-empty user data, topics from header copied to user data", + args: args{ + userData: []openrtb2.Data{ + { + ID: "1", + Name: "data1", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + }, + }, + }, + headerData: []Topic{ + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{3, 4}, + }, + }, + topicsDomain: "ads.pubmatic.com", + }, + want: []openrtb2.Data{ + { + ID: "1", + Name: "data1", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + }, + }, + { + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + {ID: "3"}, + {ID: "4"}, + }, + Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`), + }, + }, + }, + { + name: "non-empty topics, user data with invalid data.ext field, topics from header copied to user data", + args: args{ + userData: []openrtb2.Data{ + { + ID: "1", + Name: "data1", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + }, + Ext: json.RawMessage(`{`), + }, + }, + headerData: []Topic{ + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{3, 4}, + }, + }, + topicsDomain: "ads.pubmatic.com", + }, + want: []openrtb2.Data{ + { + ID: "1", + Name: "data1", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + }, + Ext: json.RawMessage(`{`), + }, + { + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + {ID: "3"}, + {ID: "4"}, + }, + Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`), + }, + }, + }, + { + name: "non-empty topics, user data with invalid topic details (invalid segtax and segclass), topics from header copied to user data", + args: args{ + userData: []openrtb2.Data{ + { + ID: "1", + Name: "chrome.com", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + }, + Ext: json.RawMessage(`{"segtax":0,"segclass":""}`), + }, + }, + headerData: []Topic{ + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{3, 4}, + }, + }, + topicsDomain: "ads.pubmatic.com", + }, + want: []openrtb2.Data{ + { + ID: "1", + Name: "chrome.com", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + }, + Ext: json.RawMessage(`{"segtax":0,"segclass":""}`), + }, + { + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + {ID: "3"}, + {ID: "4"}, + }, + Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`), + }, + }, + }, + { + name: "non-empty topics, user data with non matching topic details (different topicdomains, segtax and segclass), topics from header copied to user data", + args: args{ + userData: []openrtb2.Data{ + { + ID: "1", + Name: "chrome.com", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + }, + Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`), + }, + { + ID: "2", + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + {ID: "5"}, + {ID: "6"}, + }, + Ext: json.RawMessage(`{"segtax":601,"segclass":"3"}`), + }, + { + ID: "3", + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + {ID: "7"}, + {ID: "8"}, + }, + Ext: json.RawMessage(`{"segtax":602,"segclass":"4"}`), + }, + }, + headerData: []Topic{ + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{3, 4}, + }, + { + SegTax: 602, + SegClass: "2", + SegIDs: []int{3, 4}, + }, + }, + topicsDomain: "ads.pubmatic.com", + }, + want: []openrtb2.Data{ + { + ID: "1", + Name: "chrome.com", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + }, + Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`), + }, + { + ID: "2", + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + {ID: "5"}, + {ID: "6"}, + }, + Ext: json.RawMessage(`{"segtax":601,"segclass":"3"}`), + }, + { + ID: "3", + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + {ID: "7"}, + {ID: "8"}, + }, + Ext: json.RawMessage(`{"segtax":602,"segclass":"4"}`), + }, + { + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + {ID: "3"}, + {ID: "4"}, + }, + Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`), + }, + { + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + {ID: "3"}, + {ID: "4"}, + }, + Ext: json.RawMessage(`{"segtax":602,"segclass":"2"}`), + }, + }, + }, + { + name: "non-empty topics, user data with same topic details (matching segtax and segclass), topics from header merged with user data (filter unique segIDs)", + args: args{ + userData: []openrtb2.Data{ + { + ID: "1", + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + {ID: "3"}, + }, + Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`), + }, + }, + headerData: []Topic{ + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{2, 3, 4}, + }, + }, + topicsDomain: "ads.pubmatic.com", + }, + want: []openrtb2.Data{ + { + ID: "1", + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + {ID: "3"}, + {ID: "4"}, + }, + Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`), + }, + }, + }, + { + name: "non-empty topics, user data with duplicate topic details (matching segtax and segclass and segIDs), topics from header merged with user data (filter unique segIDs), user.data will not be deduped", + args: args{ + userData: []openrtb2.Data{ + { + ID: "1", + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + {ID: "3"}, + }, + Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`), + }, + { + ID: "1", + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + {ID: "3"}, + }, + Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`), + }, + }, + headerData: []Topic{ + { + SegTax: 600, + SegClass: "2", + SegIDs: []int{2, 3, 4}, + }, + }, + topicsDomain: "ads.pubmatic.com", + }, + want: []openrtb2.Data{ + { + ID: "1", + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + {ID: "3"}, + {ID: "4"}, + }, + Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`), + }, + { + ID: "1", + Name: "ads.pubmatic.com", + Segment: []openrtb2.Segment{ + {ID: "1"}, + {ID: "2"}, + {ID: "3"}, + }, + Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := UpdateUserDataWithTopics(tt.args.userData, tt.args.headerData, tt.args.topicsDomain) + sort.Slice(got, func(i, j int) bool { + if got[i].Name == got[j].Name { + return string(got[i].Ext) < string(got[j].Ext) + } + return got[i].Name < got[j].Name + }) + sort.Slice(tt.want, func(i, j int) bool { + if tt.want[i].Name == tt.want[j].Name { + return string(tt.want[i].Ext) < string(tt.want[j].Ext) + } + return tt.want[i].Name < tt.want[j].Name + }) + + for g := range got { + sort.Slice(got[g].Segment, func(i, j int) bool { + return got[g].Segment[i].ID < got[g].Segment[j].ID + }) + } + assert.Equal(t, tt.want, got, tt.name) + }) + } +} diff --git a/router/admin.go b/router/admin.go index 29cdbbe5e23..a90e5f233d9 100644 --- a/router/admin.go +++ b/router/admin.go @@ -5,9 +5,9 @@ import ( "net/http/pprof" "time" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/endpoints" - "github.com/prebid/prebid-server/version" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/endpoints" + "github.com/prebid/prebid-server/v3/version" ) func Admin(rateConverter *currency.RateConverter, rateConverterFetchingInterval time.Duration) *http.ServeMux { diff --git a/router/aspects/request_timeout_handler.go b/router/aspects/request_timeout_handler.go index 39a4341f995..0a4d147d908 100644 --- a/router/aspects/request_timeout_handler.go +++ b/router/aspects/request_timeout_handler.go @@ -6,8 +6,8 @@ import ( "time" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/metrics" ) func QueuedRequestTimeout(f httprouter.Handle, reqTimeoutHeaders config.RequestTimeoutHeaders, metricsEngine metrics.MetricsEngine, requestType metrics.RequestType) httprouter.Handle { diff --git a/router/aspects/request_timeout_handler_test.go b/router/aspects/request_timeout_handler_test.go index 26e546dcd40..2ac8f36ae35 100644 --- a/router/aspects/request_timeout_handler_test.go +++ b/router/aspects/request_timeout_handler_test.go @@ -8,8 +8,8 @@ import ( "time" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/metrics" "github.com/stretchr/testify/assert" ) @@ -78,7 +78,7 @@ func TestAny(t *testing.T) { reqTimeFloat, _ := strconv.ParseFloat(test.reqTimeInQueue, 64) result := ExecuteAspectRequest(t, test.reqTimeInQueue, test.reqTimeOut, test.setHeaders, metrics.ReqTypeVideo, test.requestStatusMetrics, reqTimeFloat) assert.Equal(t, test.expectedRespCode, result.Code, test.expectedRespCodeMessage) - assert.Equal(t, test.expectedRespBody, string(result.Body.Bytes()), test.expectedRespBodyMessage) + assert.Equal(t, test.expectedRespBody, result.Body.String(), test.expectedRespBodyMessage) } } @@ -101,7 +101,7 @@ func ExecuteAspectRequest(t *testing.T, timeInQueue string, reqTimeout string, s req.Header.Set(reqTimeoutHeaderName, reqTimeout) } - customHeaders := config.RequestTimeoutHeaders{reqTimeInQueueHeaderName, reqTimeoutHeaderName} + customHeaders := config.RequestTimeoutHeaders{RequestTimeInQueue: reqTimeInQueueHeaderName, RequestTimeoutInQueue: reqTimeoutHeaderName} metrics := &metrics.MetricsEngineMock{} diff --git a/router/bidder_params_tests/appnexus.json b/router/bidder_params_tests/appnexus.json new file mode 100644 index 00000000000..11dedd41e49 --- /dev/null +++ b/router/bidder_params_tests/appnexus.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Sample schema", + "description": "A sample schema to test the bidder/params endpoint", + "type": "object", + "properties": { + "integer_param": { + "type": "integer", + "minimum": 1, + "description": "A customer id" + }, + "string_param_1": { + "type": "string", + "minLength": 1, + "description": "Text with blanks in between" + }, + "string_param_2": { + "type": "string", + "minLength": 1, + "description": "Text_with_no_blanks_in_between" + } + }, + "required": [ + "integer_param", + "string_param_2" + ] +} diff --git a/router/router.go b/router/router.go index 70b7860d661..d412b5152e2 100644 --- a/router/router.go +++ b/router/router.go @@ -10,33 +10,36 @@ import ( "strings" "time" - analyticsBuild "github.com/prebid/prebid-server/analytics/build" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/endpoints" - "github.com/prebid/prebid-server/endpoints/events" - infoEndpoints "github.com/prebid/prebid-server/endpoints/info" - "github.com/prebid/prebid-server/endpoints/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/experiment/adscert" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/hooks" - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/metrics" - metricsConf "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/modules" - "github.com/prebid/prebid-server/modules/moduledeps" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - pbc "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/router/aspects" - "github.com/prebid/prebid-server/server/ssl" - storedRequestsConf "github.com/prebid/prebid-server/stored_requests/config" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/jsonutil" - "github.com/prebid/prebid-server/util/uuidutil" - "github.com/prebid/prebid-server/version" + openrtb2model "github.com/prebid/openrtb/v20/openrtb2" + analyticsBuild "github.com/prebid/prebid-server/v3/analytics/build" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/currency" + "github.com/prebid/prebid-server/v3/endpoints" + "github.com/prebid/prebid-server/v3/endpoints/events" + infoEndpoints "github.com/prebid/prebid-server/v3/endpoints/info" + "github.com/prebid/prebid-server/v3/endpoints/openrtb2" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/exchange" + "github.com/prebid/prebid-server/v3/experiment/adscert" + "github.com/prebid/prebid-server/v3/floors" + "github.com/prebid/prebid-server/v3/gdpr" + "github.com/prebid/prebid-server/v3/hooks" + "github.com/prebid/prebid-server/v3/macros" + "github.com/prebid/prebid-server/v3/metrics" + metricsConf "github.com/prebid/prebid-server/v3/metrics/config" + "github.com/prebid/prebid-server/v3/modules" + "github.com/prebid/prebid-server/v3/modules/moduledeps" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/ortb" + "github.com/prebid/prebid-server/v3/pbs" + pbc "github.com/prebid/prebid-server/v3/prebid_cache_client" + "github.com/prebid/prebid-server/v3/router/aspects" + "github.com/prebid/prebid-server/v3/server/ssl" + storedRequestsConf "github.com/prebid/prebid-server/v3/stored_requests/config" + "github.com/prebid/prebid-server/v3/usersync" + "github.com/prebid/prebid-server/v3/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/uuidutil" + "github.com/prebid/prebid-server/v3/version" _ "github.com/go-sql-driver/mysql" "github.com/golang/glog" @@ -55,11 +58,11 @@ import ( // // This function stores the file contents in memory, and should not be used on large directories. // If the root directory, or any of the files in it, cannot be read, then the program will exit. -func NewJsonDirectoryServer(schemaDirectory string, validator openrtb_ext.BidderParamValidator, aliases map[string]string) httprouter.Handle { - return newJsonDirectoryServer(schemaDirectory, validator, aliases, openrtb_ext.GetAliasBidderToParent()) +func NewJsonDirectoryServer(schemaDirectory string, validator openrtb_ext.BidderParamValidator) httprouter.Handle { + return newJsonDirectoryServer(schemaDirectory, validator, openrtb_ext.GetAliasBidderToParent()) } -func newJsonDirectoryServer(schemaDirectory string, validator openrtb_ext.BidderParamValidator, aliases map[string]string, yamlAliases map[openrtb_ext.BidderName]openrtb_ext.BidderName) httprouter.Handle { +func newJsonDirectoryServer(schemaDirectory string, validator openrtb_ext.BidderParamValidator, aliases map[openrtb_ext.BidderName]openrtb_ext.BidderName) httprouter.Handle { // Slurp the files into memory first, since they're small and it minimizes request latency. files, err := os.ReadDir(schemaDirectory) if err != nil { @@ -79,19 +82,10 @@ func newJsonDirectoryServer(schemaDirectory string, validator openrtb_ext.Bidder } // Add in any aliases - for aliasName, parentBidder := range yamlAliases { + for aliasName, parentBidder := range aliases { data[string(aliasName)] = json.RawMessage(validator.Schema(parentBidder)) } - // Add in any default aliases - for aliasName, bidderName := range aliases { - bidderData, ok := data[bidderName] - if !ok { - glog.Fatalf("Default alias (%s) exists referencing unknown bidder: %s", aliasName, bidderName) - } - data[aliasName] = bidderData - } - response, err := jsonutil.Marshal(data) if err != nil { glog.Fatalf("Failed to marshal bidder param JSON-schema: %v", err) @@ -122,7 +116,8 @@ type Router struct { *httprouter.Router MetricsEngine *metricsConf.DetailedMetricsEngine ParamsValidator openrtb_ext.BidderParamValidator - Shutdown func() + + shutdowns []func() } func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *Router, err error) { @@ -162,6 +157,16 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R }, } + floorFechterHttpClient := &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + MaxConnsPerHost: cfg.PriceFloors.Fetcher.HttpClient.MaxConnsPerHost, + MaxIdleConns: cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConns, + MaxIdleConnsPerHost: cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConnsPerHost, + IdleConnTimeout: time.Duration(cfg.PriceFloors.Fetcher.HttpClient.IdleConnTimeout) * time.Second, + }, + } + if err := checkSupportedUserSyncEndpoints(cfg.BidderInfos); err != nil { return nil, err } @@ -180,7 +185,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R syncerKeys = append(syncerKeys, k) } - moduleDeps := moduledeps.ModuleDeps{HTTPClient: generalHttpClient} + moduleDeps := moduledeps.ModuleDeps{HTTPClient: generalHttpClient, RateConvertor: rateConvertor} repo, moduleStageNames, err := modules.NewBuilder().Build(cfg.Hooks.Modules, moduleDeps) if err != nil { glog.Fatalf("Failed to init hook modules: %v", err) @@ -189,11 +194,12 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R // Metrics engine r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, openrtb_ext.CoreBidderNames(), syncerKeys, moduleStageNames) shutdown, fetcher, ampFetcher, accounts, categoriesFetcher, videoFetcher, storedRespFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router) - // todo(zachbadgett): better shutdown - r.Shutdown = shutdown analyticsRunner := analyticsBuild.New(&cfg.Analytics) + // register the analytics runner for shutdown + r.shutdowns = append(r.shutdowns, shutdown, analyticsRunner.Shutdown) + paramsValidator, err := openrtb_ext.NewBidderParamsValidator(schemaDirectory) if err != nil { glog.Fatalf("Failed to create the bidder params validator. %v", err) @@ -202,10 +208,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R activeBidders := exchange.GetActiveBidders(cfg.BidderInfos) disabledBidders := exchange.GetDisabledBidderWarningMessages(cfg.BidderInfos) - defaultAliases, defReqJSON := readDefaultRequest(cfg.DefReqConfig) - if err := validateDefaultAliases(defaultAliases); err != nil { - return nil, err - } + defReqJSON := readDefaultRequest(cfg.DefReqConfig) gvlVendorIDs := cfg.BidderInfos.ToGVLVendorIDMap() vendorListFetcher := gdpr.NewVendorListFetcher(context.Background(), cfg.GDPR, generalHttpClient, gdpr.VendorListURLMaker) @@ -224,22 +227,25 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R glog.Fatalf("Failed to create ads cert signer: %v", err) } + requestValidator := ortb.NewRequestValidator(activeBidders, disabledBidders, paramsValidator) + priceFloorFetcher := floors.NewPriceFloorFetcher(cfg.PriceFloors, floorFechterHttpClient, r.MetricsEngine) + tmaxAdjustments := exchange.ProcessTMaxAdjustments(cfg.TmaxAdjustments) planBuilder := hooks.NewExecutionPlanBuilder(cfg.Hooks, repo) macroReplacer := macros.NewStringIndexBasedReplacer() - theExchange := exchange.NewExchange(adapters, cacheClient, cfg, syncersByBidder, r.MetricsEngine, cfg.BidderInfos, gdprPermsBuilder, rateConvertor, categoriesFetcher, adsCertSigner, macroReplacer) + theExchange := exchange.NewExchange(adapters, cacheClient, cfg, requestValidator, syncersByBidder, r.MetricsEngine, cfg.BidderInfos, gdprPermsBuilder, rateConvertor, categoriesFetcher, adsCertSigner, macroReplacer, priceFloorFetcher) var uuidGenerator uuidutil.UUIDRandomGenerator - openrtbEndpoint, err := openrtb2.NewEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, analyticsRunner, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder, tmaxAdjustments) + openrtbEndpoint, err := openrtb2.NewEndpoint(uuidGenerator, theExchange, requestValidator, fetcher, accounts, cfg, r.MetricsEngine, analyticsRunner, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder, tmaxAdjustments) if err != nil { glog.Fatalf("Failed to create the openrtb2 endpoint handler. %v", err) } - ampEndpoint, err := openrtb2.NewAmpEndpoint(uuidGenerator, theExchange, paramsValidator, ampFetcher, accounts, cfg, r.MetricsEngine, analyticsRunner, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder, tmaxAdjustments) + ampEndpoint, err := openrtb2.NewAmpEndpoint(uuidGenerator, theExchange, requestValidator, ampFetcher, accounts, cfg, r.MetricsEngine, analyticsRunner, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder, tmaxAdjustments) if err != nil { glog.Fatalf("Failed to create the amp endpoint handler. %v", err) } - videoEndpoint, err := openrtb2.NewVideoEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, videoFetcher, accounts, cfg, r.MetricsEngine, analyticsRunner, disabledBidders, defReqJSON, activeBidders, cacheClient, tmaxAdjustments) + videoEndpoint, err := openrtb2.NewVideoEndpoint(uuidGenerator, theExchange, requestValidator, fetcher, videoFetcher, accounts, cfg, r.MetricsEngine, analyticsRunner, disabledBidders, defReqJSON, activeBidders, cacheClient, tmaxAdjustments) if err != nil { glog.Fatalf("Failed to create the video endpoint handler. %v", err) } @@ -252,9 +258,9 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R r.POST("/openrtb2/auction", openrtbEndpoint) r.POST("/openrtb2/video", videoEndpoint) r.GET("/openrtb2/amp", ampEndpoint) - r.GET("/info/bidders", infoEndpoints.NewBiddersEndpoint(cfg.BidderInfos, defaultAliases)) - r.GET("/info/bidders/:bidderName", infoEndpoints.NewBiddersDetailEndpoint(cfg.BidderInfos, defaultAliases)) - r.GET("/bidders/params", NewJsonDirectoryServer(schemaDirectory, paramsValidator, defaultAliases)) + r.GET("/info/bidders", infoEndpoints.NewBiddersEndpoint(cfg.BidderInfos)) + r.GET("/info/bidders/:bidderName", infoEndpoints.NewBiddersDetailEndpoint(cfg.BidderInfos)) + r.GET("/bidders/params", NewJsonDirectoryServer(schemaDirectory, paramsValidator)) r.POST("/cookie_sync", endpoints.NewCookieSyncEndpoint(syncersByBidder, cfg, gdprPermsBuilder, tcf2CfgBuilder, r.MetricsEngine, analyticsRunner, accounts, activeBidders).Handle) r.GET("/status", endpoints.NewStatusEndpoint(cfg.StatusResponse)) r.GET("/", serveIndex) @@ -286,6 +292,15 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R return r, nil } +// Shutdown closes any dependencies of the router that may need closing +func (r *Router) Shutdown() { + glog.Info("[PBS Router] shutting down") + for _, shutdown := range r.shutdowns { + shutdown() + } + glog.Info("[PBS Router] shut down") +} + func checkSupportedUserSyncEndpoints(bidderInfos config.BidderInfos) error { for name, info := range bidderInfos { if info.Syncer == nil { @@ -336,56 +351,31 @@ func SupportCORS(handler http.Handler) http.Handler { return c.Handler(handler) } -type defReq struct { - Ext defExt `json:"ext"` -} -type defExt struct { - Prebid defaultAliases `json:"prebid"` -} -type defaultAliases struct { - Aliases map[string]string `json:"aliases"` -} - -func readDefaultRequest(defReqConfig config.DefReqConfig) (map[string]string, []byte) { - defReq := &defReq{} - aliases := make(map[string]string) - if defReqConfig.Type == "file" { - if len(defReqConfig.FileSystem.FileName) == 0 { - return aliases, []byte{} - } - defReqJSON, err := os.ReadFile(defReqConfig.FileSystem.FileName) - if err != nil { - glog.Fatalf("error reading aliases from file %s: %v", defReqConfig.FileSystem.FileName, err) - return aliases, []byte{} - } - - if err := jsonutil.UnmarshalValid(defReqJSON, defReq); err != nil { - // we might not have aliases defined, but will atleast show that the JSON file is parsable. - glog.Fatalf("error parsing alias json in file %s: %v", defReqConfig.FileSystem.FileName, err) - return aliases, []byte{} - } - - // Read in the alias map if we want to populate the info endpoints with aliases. - if defReqConfig.AliasInfo { - aliases = defReq.Ext.Prebid.Aliases - } - return aliases, defReqJSON +func readDefaultRequest(defReqConfig config.DefReqConfig) []byte { + switch defReqConfig.Type { + case "file": + return readDefaultRequestFromFile(defReqConfig) + default: + return []byte{} } - return aliases, []byte{} } -func validateDefaultAliases(aliases map[string]string) error { - var errs []error +func readDefaultRequestFromFile(defReqConfig config.DefReqConfig) []byte { + if len(defReqConfig.FileSystem.FileName) == 0 { + return []byte{} + } - for alias := range aliases { - if openrtb_ext.IsBidderNameReserved(alias) { - errs = append(errs, fmt.Errorf("alias %s is a reserved bidder name and cannot be used", alias)) - } + defaultRequestJSON, err := os.ReadFile(defReqConfig.FileSystem.FileName) + if err != nil { + glog.Fatalf("error reading default request from file %s: %v", defReqConfig.FileSystem.FileName, err) + return []byte{} } - if len(errs) > 0 { - return errortypes.NewAggregateError("default request alias errors", errs) + // validate json is valid + if err := jsonutil.UnmarshalValid(defaultRequestJSON, &openrtb2model.BidRequest{}); err != nil { + glog.Fatalf("error parsing default request from file %s: %v", defReqConfig.FileSystem.FileName, err) + return []byte{} } - return nil + return defaultRequestJSON } diff --git a/router/router_test.go b/router/router_test.go index f4f7715e6c3..08d7468446e 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -7,9 +7,10 @@ import ( "os" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + jsoniter "github.com/json-iterator/go" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -18,6 +19,11 @@ const adapterDirectory = "../adapters" type testValidator struct{} +func TestMain(m *testing.M) { + jsoniter.RegisterExtension(&jsonutil.RawMessageExtension{}) + os.Exit(m.Run()) +} + func (validator *testValidator) Validate(name openrtb_ext.BidderName, ext json.RawMessage) error { return nil } @@ -38,9 +44,8 @@ func ensureHasKey(t *testing.T, data map[string]json.RawMessage, key string) { } func TestNewJsonDirectoryServer(t *testing.T) { - defaultAlias := map[string]string{"aliastest": "appnexus"} - yamlAlias := map[openrtb_ext.BidderName]openrtb_ext.BidderName{openrtb_ext.BidderName("alias"): openrtb_ext.BidderName("parentAlias")} - handler := newJsonDirectoryServer("../static/bidder-params", &testValidator{}, defaultAlias, yamlAlias) + alias := map[openrtb_ext.BidderName]openrtb_ext.BidderName{openrtb_ext.BidderName("alias"): openrtb_ext.BidderName("parentAlias")} + handler := newJsonDirectoryServer("../static/bidder-params", &testValidator{}, alias) recorder := httptest.NewRecorder() request, _ := http.NewRequest("GET", "/whatever", nil) handler(recorder, request, nil) @@ -60,7 +65,6 @@ func TestNewJsonDirectoryServer(t *testing.T) { } } - ensureHasKey(t, data, "aliastest") ensureHasKey(t, data, "alias") } @@ -205,73 +209,22 @@ func TestNoCache(t *testing.T) { } } -var testDefReqConfig = config.DefReqConfig{ - Type: "file", - FileSystem: config.DefReqFiles{ - FileName: "test_aliases.json", - }, - AliasInfo: true, -} - -func TestLoadDefaultAliases(t *testing.T) { - defAliases, aliasJSON := readDefaultRequest(testDefReqConfig) - expectedJSON := []byte(`{"ext":{"prebid":{"aliases": {"test1": "appnexus", "test2": "rubicon", "test3": "openx"}}}}`) - expectedAliases := map[string]string{ - "test1": "appnexus", - "test2": "rubicon", - "test3": "openx", - } - - assert.JSONEq(t, string(expectedJSON), string(aliasJSON)) - assert.Equal(t, expectedAliases, defAliases) -} +func TestBidderParamsCompactedOutput(t *testing.T) { + expectedFormattedResponse := `{"appnexus":{"$schema":"http://json-schema.org/draft-04/schema#","title":"Sample schema","description":"A sample schema to test the bidder/params endpoint","type":"object","properties":{"integer_param":{"type":"integer","minimum":1,"description":"A customer id"},"string_param_1":{"type":"string","minLength":1,"description":"Text with blanks in between"},"string_param_2":{"type":"string","minLength":1,"description":"Text_with_no_blanks_in_between"}},"required":["integer_param","string_param_2"]}}` -func TestLoadDefaultAliasesNoInfo(t *testing.T) { - noInfoConfig := testDefReqConfig - noInfoConfig.AliasInfo = false - defAliases, aliasJSON := readDefaultRequest(noInfoConfig) - expectedJSON := []byte(`{"ext":{"prebid":{"aliases": {"test1": "appnexus", "test2": "rubicon", "test3": "openx"}}}}`) - expectedAliases := map[string]string{} + // Setup + inSchemaDirectory := "bidder_params_tests" + paramsValidator, err := openrtb_ext.NewBidderParamsValidator(inSchemaDirectory) + assert.NoError(t, err, "Error initialing validator") - assert.JSONEq(t, string(expectedJSON), string(aliasJSON)) - assert.Equal(t, expectedAliases, defAliases) -} - -func TestValidateDefaultAliases(t *testing.T) { - var testCases = []struct { - description string - givenAliases map[string]string - expectedError string - }{ - { - description: "None", - givenAliases: map[string]string{}, - expectedError: "", - }, - { - description: "Valid", - givenAliases: map[string]string{"aAlias": "a"}, - expectedError: "", - }, - { - description: "Invalid", - givenAliases: map[string]string{"all": "a"}, - expectedError: "default request alias errors (1 error):\n 1: alias all is a reserved bidder name and cannot be used\n", - }, - { - description: "Mixed", - givenAliases: map[string]string{"aAlias": "a", "all": "a"}, - expectedError: "default request alias errors (1 error):\n 1: alias all is a reserved bidder name and cannot be used\n", - }, - } + handler := newJsonDirectoryServer(inSchemaDirectory, paramsValidator, nil) + recorder := httptest.NewRecorder() + request, err := http.NewRequest("GET", "/bidder/params", nil) + assert.NoError(t, err, "Error creating request") - for _, test := range testCases { - err := validateDefaultAliases(test.givenAliases) + // Run + handler(recorder, request, nil) - if test.expectedError == "" { - assert.NoError(t, err, test.description) - } else { - assert.EqualError(t, err, test.expectedError, test.description) - } - } + // Assertions + assert.Equal(t, expectedFormattedResponse, recorder.Body.String()) } diff --git a/router/test_aliases.json b/router/test_aliases.json deleted file mode 100644 index e1be6455664..00000000000 --- a/router/test_aliases.json +++ /dev/null @@ -1,12 +0,0 @@ - -{ - "ext": { - "prebid": { - "aliases": { - "test1": "appnexus", - "test2": "rubicon", - "test3": "openx" - } - } - } -} \ No newline at end of file diff --git a/sample/001_banner/app.yaml b/sample/001_banner/app.yaml new file mode 100644 index 00000000000..a314e77e9e4 --- /dev/null +++ b/sample/001_banner/app.yaml @@ -0,0 +1,20 @@ +port: 8000 # main auction server port +admin_port: 6060 # admin server listening port + +external_url: localhost # host url of all 2 servers above +status_response: "ok" # default response string for /status endpoint + +gdpr: + default_value: "0" # disable gdpr, explicitly specifying a default value is a requirement in prebid server config + +# set up stored request storage using local file system +stored_requests: + filesystem: + enabled: true + directorypath: ./stored_requests/data/by_id + +# set up stored response storage using local file system +stored_responses: + filesystem: + enabled: true + directorypath: ./stored_responses/data/by_id \ No newline at end of file diff --git a/sample/001_banner/pbjs.html b/sample/001_banner/pbjs.html new file mode 100644 index 00000000000..af6ad643eec --- /dev/null +++ b/sample/001_banner/pbjs.html @@ -0,0 +1,121 @@ + + + + + + + + + + + +

001_banner

+

+ This demo uses Prebid.js to interact with Prebid Server to fill the ad slot test-div-1 + The auction request to Prebid Server uses a stored request, which in turn links to a stored response.
+ Look for the /auction request in your browser's developer tool to inspect the request + and response. +

+

↓I am ad unit test-div-1 ↓

+
+
+ + diff --git a/sample/001_banner/stored_request.json b/sample/001_banner/stored_request.json new file mode 100644 index 00000000000..59e13ae8295 --- /dev/null +++ b/sample/001_banner/stored_request.json @@ -0,0 +1,28 @@ +{ + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "pubmatic": { + "publisherId": "111111", + "adSlot": "test" + } + }, + "storedbidresponse": [ + { "bidder": "pubmatic", "id": "test-bid-id" } + ] + } + } +} diff --git a/sample/001_banner/stored_response.json b/sample/001_banner/stored_response.json new file mode 100644 index 00000000000..033d992872f --- /dev/null +++ b/sample/001_banner/stored_response.json @@ -0,0 +1,46 @@ +{ + "id": "test-auction-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "test-div-1", + "price": 1, + "adm": "", + "adomain": [ + "www.addomain.com" + ], + "iurl": "http://localhost11", + "crid": "creative111", + "w": 300, + "h": 250, + "mtype": 1, + "ext": { + "bidtype": 0, + "dspid": 6, + "origbidcpm": 1, + "origbidcur": "USD", + "prebid": { + "meta": { + "adaptercode": "pubmatic" + }, + "targeting": { + "hb_bidder": "pubmatic", + "hb_pb": "1.00", + "hb_size": "300x250" + }, + "type": "banner", + "video": { + "duration": 0, + "primary_category": "" + } + } + } + } + ], + "seat": "pubmatic" + } + ], + "cur": "USD" +} diff --git a/sample/README.md b/sample/README.md new file mode 100644 index 00000000000..6b0109aa263 --- /dev/null +++ b/sample/README.md @@ -0,0 +1,65 @@ +# Sample + +The Sample describes several demos of quickly spinning up different Prebid Server instances with various preset configurations. These samples are intended for audiences with little knowledge about Prebid Server and plan to play around with it locally and see how it works. + +# Installation + +In the Sample, we use `docker` and `docker-compose` to instantiate examples; with docker providing a unified setup and interface, you can spin up a demo server instance locally with only one command without knowing all the complexities. +The docker image used in `docker-compose.yml` is the `Dockerfile` residing in the root level of the repository. + +## Option 1 - Standard Docker Engine +Install `docker` and `docker-compose` via the [official docker page](https://docs.docker.com/compose/install/#scenario-one-install-docker-desktop). If you cannot use the official docker engine due to restrictions of its license, see the option below about using Podman instead of Docker. + +## Option 2 - Podman +From MacOS, you can use [podman](https://podman.io/) with these additional steps: + +```sh +$ brew install podman docker-compose +$ podman machine init +$ podman machine set --rootful +$ podman machine start +``` + +# Examples + +## Common File & Structures +All required files for each example are stored in a folder that follows the name pattern _. The `` suggests its order and `` describes its title. + +The following files will be present for every example and are exclusively catered to that example. +1. `app.yaml` - the prebid server app config. +2. `pbjs.html` - the HTML file with `Prebid JS` integration and communicates with the Prebid Server. It also provides a detailed explanation of the example. +3. `*.json` - additional files required to support the example. e.g. stored request and stored response. + +## Common steps + +```sh +#1 - To get to the sample folder if you are on the root repository directory. +$ cd sample + +#2a - This command builds a new image, you should execute this command whenever the repository source code changes. +$ docker-compose build + +#2b - Optionally you could run `docker-compose build --no-cache` if you want to build an completely new image without using cache but results in slower time to build it. +$ docker-compose build --no-cache + +#3a - Spin up a corresponding sample in a container - see Steps for details +$ docker-compose up _ + +#3b - Optionally you could use `--force-recreate` flag if you want to recreate the container every time you spin up the container. +$ docker-compose up _ --force-recreate +``` + +### Detailed Steps +1. To prevent `app.yaml` from being overwritten by other config files. Ensure that `pbs.yaml` or `pbs.json` config file **MUST NOT** be present in the root directory of the repository. + +2. Bring up an instance by running `docker-compose up _` in the `sample` folder. + +3. Wait patiently until you see ` Admin server starting on: :6060` and `Main server starting on: :8000` in the command line output. This marks the Prebid Server instance finishing its initialization and is ready to serve the auction traffic. + +4. you can copy the URL `http://localhost:8000/status` and paste it into your browser. You should see `ok` in the response which is another way to tell the Prebid Server that the main auction server is up and running. + +5. Open a new tab in your browser and turn on the console UI. If you are using Chrome, you can right-click on the page and click `inspect`. Once the console UI is on, click on the `Network` tab to inspect the traffic later. + +6. Copy the URL `http://localhost:8000/static/pbjs.html?pbjs_debug=true` into your browser. It starts the example immediately with debugging information from `Prebid JS`, and you can inspect the request and response between `Prebid JS` and `Prebid Server`. + +7. After playing with the example, type `docker-compose down`. This is to shut down the existing Sample so you can start the next one you want to select. diff --git a/sample/docker-compose.yml b/sample/docker-compose.yml new file mode 100644 index 00000000000..49b615c1b34 --- /dev/null +++ b/sample/docker-compose.yml @@ -0,0 +1,20 @@ +version: "3.9" +services: + 001_banner: + platform: linux/amd64 + build: + context: ../ + dockerfile: Dockerfile + args: + - TEST=false + image: pbs-sample + container_name: 001_banner + privileged: true + ports: + - "8000:8000" + - "6060:6060" + volumes: + - ../sample/001_banner/app.yaml:/usr/local/bin/pbs.yaml + - ../sample/001_banner/pbjs.html:/usr/local/bin/static/pbjs.html + - ../sample/001_banner/stored_request.json:/usr/local/bin/stored_requests/data/by_id/stored_imps/test-imp-id.json + - ../sample/001_banner/stored_response.json:/usr/local/bin/stored_responses/data/by_id/stored_responses/test-bid-id.json \ No newline at end of file diff --git a/schain/schain.go b/schain/schain.go index 6f084a65a2a..cdd1e144498 100644 --- a/schain/schain.go +++ b/schain/schain.go @@ -3,9 +3,9 @@ package schain import ( "fmt" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // BidderToPrebidSChains organizes the ORTB 2.5 multiple root schain nodes into a map of schain nodes by bidder diff --git a/schain/schain_test.go b/schain/schain_test.go index dbe38d4014b..f112c9ec7ed 100644 --- a/schain/schain_test.go +++ b/schain/schain_test.go @@ -3,8 +3,8 @@ package schain import ( "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/schain/schainwriter.go b/schain/schainwriter.go index e7c9dd4ce72..37b62c993f2 100644 --- a/schain/schainwriter.go +++ b/schain/schainwriter.go @@ -1,9 +1,8 @@ package schain import ( - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // NewSChainWriter creates an ORTB 2.5 schain writer instance @@ -34,9 +33,9 @@ type SChainWriter struct { // Write selects an schain from the multi-schain ORTB 2.5 location (req.ext.prebid.schains) for the specified bidder // and copies it to the ORTB 2.5 location (req.source.ext). If no schain exists for the bidder in the multi-schain // location and no wildcard schain exists, the request is not modified. -func (w SChainWriter) Write(req *openrtb2.BidRequest, bidder string) { +func (w SChainWriter) Write(reqWrapper *openrtb_ext.RequestWrapper, bidder string) { const sChainWildCard = "*" - var selectedSChain *openrtb2.SupplyChain + var selectedSChain openrtb2.SupplyChain wildCardSChain := w.sChainsByBidder[sChainWildCard] bidderSChain := w.sChainsByBidder[bidder] @@ -46,32 +45,27 @@ func (w SChainWriter) Write(req *openrtb2.BidRequest, bidder string) { return } - selectedSChain = &openrtb2.SupplyChain{Ver: "1.0"} + selectedSChain = openrtb2.SupplyChain{Ver: "1.0"} if bidderSChain != nil { - selectedSChain = bidderSChain + selectedSChain = *bidderSChain } else if wildCardSChain != nil { - selectedSChain = wildCardSChain + selectedSChain = *wildCardSChain } - schain := openrtb_ext.ExtRequestPrebidSChain{ - SChain: *selectedSChain, - } - - if req.Source == nil { - req.Source = &openrtb2.Source{} + if reqWrapper.Source == nil { + reqWrapper.Source = &openrtb2.Source{} } else { - sourceCopy := *req.Source - req.Source = &sourceCopy + // Copy Source to avoid shared memory issues. + // Source may be modified differently for different bidders in request + sourceCopy := *reqWrapper.Source + reqWrapper.Source = &sourceCopy } - if w.hostSChainNode != nil { - schain.SChain.Nodes = append(schain.SChain.Nodes, *w.hostSChainNode) - } + reqWrapper.Source.SChain = &selectedSChain - sourceExt, err := jsonutil.Marshal(schain) - if err == nil { - req.Source.Ext = sourceExt + if w.hostSChainNode != nil { + reqWrapper.Source.SChain.Nodes = append(reqWrapper.Source.SChain.Nodes, *w.hostSChainNode) } } diff --git a/schain/schainwriter_test.go b/schain/schainwriter_test.go index 9288b531d56..69fda959078 100644 --- a/schain/schainwriter_test.go +++ b/schain/schainwriter_test.go @@ -4,9 +4,10 @@ import ( "encoding/json" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" + + "github.com/prebid/openrtb/v20/openrtb2" "github.com/stretchr/testify/assert" ) @@ -16,206 +17,348 @@ func TestSChainWriter(t *testing.T) { const seller2SChain string = `"schain":{"complete":2,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":2}],"ver":"2.0"}` const seller3SChain string = `"schain":{"complete":3,"nodes":[{"asi":"directseller3.com","sid":"00003","rid":"BidRequest3","hp":3}],"ver":"3.0"}` const sellerWildCardSChain string = `"schain":{"complete":1,"nodes":[{"asi":"wildcard1.com","sid":"wildcard1","rid":"WildcardReq1","hp":1}],"ver":"1.0"}` - const hostNode string = `{"asi":"pbshostcompany.com","sid":"00001","rid":"BidRequest","hp":1}` const seller1Node string = `{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}` tests := []struct { description string - giveRequest openrtb2.BidRequest + giveRequest *openrtb_ext.RequestWrapper giveBidder string giveHostSChain *openrtb2.SupplyChainNode - wantRequest openrtb2.BidRequest + wantRequest *openrtb_ext.RequestWrapper wantError bool }{ { description: "nil source, nil ext.prebid.schains and empty host schain", - giveRequest: openrtb2.BidRequest{ - Ext: nil, - Source: nil, + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: nil, + Source: nil, + }, }, + giveBidder: "appnexus", giveHostSChain: nil, - wantRequest: openrtb2.BidRequest{ - Ext: nil, - Source: nil, + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: nil, + Source: nil, + }, }, }, { - description: "Use source schain -- no bidder schain or wildcard schain in nil ext.prebid.schains", - giveRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller2SChain + `}`), + description: "Use source schain -- no bidder schain or wildcard schain in nil ext.prebid.schains - so source.schain is set and unmodified", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{}`), + Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{ + Ver: "1.1", + }, + }, }, }, giveBidder: "appnexus", - wantRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller2SChain + `}`), + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{}`), + Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{ + Ver: "1.1", + }, + }, }, }, }, { - description: "Use source schain -- no bidder schain or wildcard schain in not nil ext.prebid.schains", - giveRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller2SChain + `}`), + description: "Use source schain -- no bidder schain or wildcard schain in not nil ext.prebid.schains - so source.schain is set and unmodified", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), + Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{ + Ver: "1.1", + }, + Ext: json.RawMessage(`{"some":"data"}`), + }, }, }, giveBidder: "rubicon", - wantRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller2SChain + `}`), + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), + Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{ + Ver: "1.1", + }, + Ext: json.RawMessage(`{"some":"data"}`), + }, }, }, }, { - description: "Use schain for bidder in ext.prebid.schains; ensure other ext.source field values are retained.", - giveRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), - Source: &openrtb2.Source{ - FD: 1, - TID: "tid data", - PChain: "pchain data", - Ext: json.RawMessage(`{` + seller2SChain + `}`), + description: "Use schain for bidder in ext.prebid.schains; ensure other source field values are retained.", + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), + Source: &openrtb2.Source{ + FD: openrtb2.Int8Ptr(1), + TID: "tid data", + PChain: "pchain data", + Ext: json.RawMessage(`{"some":"data"}`), + }, }, }, giveBidder: "appnexus", - wantRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), - Source: &openrtb2.Source{ - FD: 1, - TID: "tid data", - PChain: "pchain data", - Ext: json.RawMessage(`{` + seller1SChain + `}`), + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), + Source: &openrtb2.Source{ + FD: openrtb2.Int8Ptr(1), + TID: "tid data", + PChain: "pchain data", + SChain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller1.com", + SID: "00001", + RID: "BidRequest1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, + Ext: json.RawMessage(`{"some":"data"}`), + }, }, }, }, { description: "Use schain for bidder in ext.prebid.schains, nil req.source ", - giveRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), - Source: nil, + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), + Source: nil, + }, }, giveBidder: "appnexus", - wantRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller1SChain + `}`), + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), + Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller1.com", + SID: "00001", + RID: "BidRequest1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, + Ext: nil, + }, }, }, }, { description: "Use wildcard schain in ext.prebid.schains.", - giveRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],` + sellerWildCardSChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: nil, + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],` + sellerWildCardSChain + `}]}}`), + Source: &openrtb2.Source{ + Ext: nil, + }, }, }, giveBidder: "appnexus", - wantRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],` + sellerWildCardSChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + sellerWildCardSChain + `}`), + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],` + sellerWildCardSChain + `}]}}`), + Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "wildcard1.com", + SID: "wildcard1", + RID: "WildcardReq1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, + Ext: nil, + }, }, }, }, { description: "Use schain for bidder in ext.prebid.schains instead of wildcard.", - giveRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["*"],` + sellerWildCardSChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: nil, + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["*"],` + sellerWildCardSChain + `}]}}`), + Source: &openrtb2.Source{ + Ext: nil, + }, }, }, giveBidder: "appnexus", - wantRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["*"],` + sellerWildCardSChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller1SChain + `}`), + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["*"],` + sellerWildCardSChain + `}]}}`), + Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller1.com", + SID: "00001", + RID: "BidRequest1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, + Ext: nil, + }, }, }, }, { description: "Use source schain -- multiple (two) bidder schains in ext.prebid.schains.", - giveRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller3SChain + `}`), + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`), + Source: &openrtb2.Source{ + Ext: json.RawMessage(`{` + seller3SChain + `}`), + }, }, }, giveBidder: "appnexus", - wantRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{` + seller3SChain + `}`), + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`), + Source: &openrtb2.Source{ + Ext: json.RawMessage(`{` + seller3SChain + `}`), + }, }, }, wantError: true, }, { description: "Schain in request, host schain defined, source.ext for bidder request should update with appended host schain", - giveRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["testbidder"],"schain":{"complete":1,"nodes":[` + seller1Node + `],"ver":"1.0"}}]}}`), - Source: nil, + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["testbidder"],"schain":{"complete":1,"nodes":[` + seller1Node + `],"ver":"1.0"}}]}}`), + Source: nil, + }, }, giveBidder: "testbidder", giveHostSChain: &openrtb2.SupplyChainNode{ ASI: "pbshostcompany.com", SID: "00001", RID: "BidRequest", HP: openrtb2.Int8Ptr(1), }, - wantRequest: openrtb2.BidRequest{ - Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["testbidder"],"schain":{"complete":1,"nodes":[` + seller1Node + `],"ver":"1.0"}}]}}`), - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{"schain":{"complete":1,"nodes":[` + seller1Node + `,` + hostNode + `],"ver":"1.0"}}`), + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["testbidder"],"schain":{"complete":1,"nodes":[` + seller1Node + `],"ver":"1.0"}}]}}`), + Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{ + Complete: 1, + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "directseller1.com", + SID: "00001", + RID: "BidRequest1", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + { + ASI: "pbshostcompany.com", + SID: "00001", + RID: "BidRequest", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, + Ext: nil, + }, }, }, }, { description: "No Schain in request, host schain defined, source.ext for bidder request should have just the host schain", - giveRequest: openrtb2.BidRequest{ - Ext: nil, - Source: nil, + giveRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: nil, + Source: nil, + }, }, giveBidder: "testbidder", giveHostSChain: &openrtb2.SupplyChainNode{ ASI: "pbshostcompany.com", SID: "00001", RID: "BidRequest", HP: openrtb2.Int8Ptr(1), }, - wantRequest: openrtb2.BidRequest{ - Ext: nil, - Source: &openrtb2.Source{ - Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":[` + hostNode + `],"ver":"1.0"}}`), + wantRequest: &openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + Ext: nil, + Source: &openrtb2.Source{ + SChain: &openrtb2.SupplyChain{ + Ver: "1.0", + Ext: nil, + Nodes: []openrtb2.SupplyChainNode{ + { + ASI: "pbshostcompany.com", + SID: "00001", + RID: "BidRequest", + HP: openrtb2.Int8Ptr(1), + Ext: nil, + }, + }, + }, + Ext: nil, + }, }, }, }, } for _, tt := range tests { - // unmarshal ext to get schains object needed to initialize writer - var reqExt *openrtb_ext.ExtRequest - if tt.giveRequest.Ext != nil { - reqExt = &openrtb_ext.ExtRequest{} - err := jsonutil.UnmarshalValid(tt.giveRequest.Ext, reqExt) - if err != nil { - t.Error("Unable to unmarshal request.ext") + t.Run(tt.description, func(t *testing.T) { + // unmarshal ext to get schains object needed to initialize writer + var reqExt *openrtb_ext.ExtRequest + if tt.giveRequest.Ext != nil { + reqExt = &openrtb_ext.ExtRequest{} + err := jsonutil.UnmarshalValid(tt.giveRequest.Ext, reqExt) + if err != nil { + t.Error("Unable to unmarshal request.ext") + } } - } - writer, err := NewSChainWriter(reqExt, tt.giveHostSChain) + writer, err := NewSChainWriter(reqExt, tt.giveHostSChain) - if tt.wantError { - assert.NotNil(t, err) - assert.Nil(t, writer) - } else { - assert.Nil(t, err) - assert.NotNil(t, writer) + if tt.wantError { + assert.NotNil(t, err) + assert.Nil(t, writer) + } else { + assert.Nil(t, err) + assert.NotNil(t, writer) - writer.Write(&tt.giveRequest, tt.giveBidder) + writer.Write(tt.giveRequest, tt.giveBidder) - assert.Equal(t, tt.wantRequest, tt.giveRequest, tt.description) - } + assert.Equal(t, tt.wantRequest, tt.giveRequest, tt.description) + } + }) } } diff --git a/scripts/check_coverage.sh b/scripts/check_coverage.sh index 0dd6235b96b..63e89297c42 100755 --- a/scripts/check_coverage.sh +++ b/scripts/check_coverage.sh @@ -25,8 +25,7 @@ while IFS= read -r LINE; do if [[ $LINE =~ "%" ]]; then PERCENT=$(echo "$LINE"|cut -d: -f2-|cut -d% -f1|cut -d. -f1|tr -d ' ') if [[ $PERCENT -lt $COV_MIN ]]; then - echo "Package has less than ${COV_MIN}% code coverage. Run ./scripts/coverage.sh --html to see a detailed coverage report, and add tests to improve your coverage" - exit 1 + echo "WARNING: Package has less than ${COV_MIN}% code coverage. Run ./scripts/coverage.sh --html to see a detailed coverage report, and add tests to improve your coverage" fi fi done <<< "$OUTPUT" diff --git a/server/listener.go b/server/listener.go index 43917ac0a05..3bc1f2e66fe 100644 --- a/server/listener.go +++ b/server/listener.go @@ -6,7 +6,7 @@ import ( "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v3/metrics" ) // monitorableListener tracks any opened connections in the metrics. diff --git a/server/listener_test.go b/server/listener_test.go index d10a3bdfbf9..293890a1e13 100644 --- a/server/listener_test.go +++ b/server/listener_test.go @@ -6,8 +6,8 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/metrics" gometrics "github.com/rcrowley/go-metrics" ) diff --git a/server/prometheus.go b/server/prometheus.go index 33114c86a0b..98409e6a588 100644 --- a/server/prometheus.go +++ b/server/prometheus.go @@ -7,8 +7,8 @@ import ( "github.com/golang/glog" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/prebid/prebid-server/config" - metricsconfig "github.com/prebid/prebid-server/metrics/config" + "github.com/prebid/prebid-server/v3/config" + metricsconfig "github.com/prebid/prebid-server/v3/metrics/config" ) func newPrometheusServer(cfg *config.Configuration, metrics *metricsconfig.DetailedMetricsEngine) *http.Server { diff --git a/server/server.go b/server/server.go index 9282f0fcf15..9461f3f451d 100644 --- a/server/server.go +++ b/server/server.go @@ -13,9 +13,9 @@ import ( "github.com/NYTimes/gziphandler" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - metricsconfig "github.com/prebid/prebid-server/metrics/config" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/metrics" + metricsconfig "github.com/prebid/prebid-server/v3/metrics/config" ) // Listen blocks forever, serving PBS requests on the given port. This will block forever, until the process is shut down. @@ -29,9 +29,6 @@ func Listen(cfg *config.Configuration, handler http.Handler, adminHandler http.H stopPrometheus := make(chan os.Signal) done := make(chan struct{}) - adminServer := newAdminServer(cfg, adminHandler) - go shutdownAfterSignals(adminServer, stopAdmin, done) - if cfg.UnixSocketEnable && len(cfg.UnixSocketName) > 0 { // start the unix_socket server if config enable-it. var ( socketListener net.Listener @@ -56,12 +53,17 @@ func Listen(cfg *config.Configuration, handler http.Handler, adminHandler http.H go runServer(mainServer, "Main", mainListener) } - var adminListener net.Listener - if adminListener, err = newTCPListener(adminServer.Addr, nil); err != nil { - glog.Errorf("Error listening for TCP connections on %s: %v for admin server", adminServer.Addr, err) - return + if cfg.Admin.Enabled { + adminServer := newAdminServer(cfg, adminHandler) + go shutdownAfterSignals(adminServer, stopAdmin, done) + + var adminListener net.Listener + if adminListener, err = newTCPListener(adminServer.Addr, nil); err != nil { + glog.Errorf("Error listening for TCP connections on %s: %v for admin server", adminServer.Addr, err) + return + } + go runServer(adminServer, "Admin", adminListener) } - go runServer(adminServer, "Admin", adminListener) if cfg.Metrics.Prometheus.Port != 0 { var ( @@ -70,7 +72,7 @@ func Listen(cfg *config.Configuration, handler http.Handler, adminHandler http.H ) 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) + glog.Errorf("Error listening for TCP connections on %s: %v for prometheus server", prometheusServer.Addr, err) return } diff --git a/server/server_test.go b/server/server_test.go index 7af892d3567..6d2a3335e3a 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -9,9 +9,10 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" - metricsconfig "github.com/prebid/prebid-server/metrics/config" + "github.com/prebid/prebid-server/v3/config" + metricsconfig "github.com/prebid/prebid-server/v3/metrics/config" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestServerShutdown(t *testing.T) { @@ -21,7 +22,7 @@ func TestServerShutdown(t *testing.T) { stopper := make(chan os.Signal) done := make(chan struct{}) go shutdownAfterSignals(server, stopper, done) - go server.Serve(ln) + go server.Serve(ln) //nolint: errcheck stopper <- os.Interrupt <-done @@ -73,7 +74,8 @@ func TestNewSocketServer(t *testing.T) { } ret := newSocketServer(cfg, nil) - assert.NotEqual(t, nil, ret, "ret : isNil()") + require.NotNil(t, ret, "ret : isNil()") + assert.Equal(t, mockServer.Addr, ret.Addr, fmt.Sprintf("Addr invalide: %v != %v", ret.Addr, mockServer.Addr)) assert.Equal(t, mockServer.ReadTimeout, ret.ReadTimeout, fmt.Sprintf("ReadTimeout invalide: %v != %v", @@ -81,9 +83,7 @@ func TestNewSocketServer(t *testing.T) { assert.Equal(t, mockServer.WriteTimeout, ret.WriteTimeout, fmt.Sprintf("WriteTimeout invalide: %v != %v", ret.WriteTimeout, mockServer.WriteTimeout)) - if ret != nil { - ret.Close() - } + ret.Close() } func TestNewMainServer(t *testing.T) { @@ -103,7 +103,8 @@ func TestNewMainServer(t *testing.T) { } ret := newMainServer(cfg, nil) - assert.NotEqual(t, nil, ret, "ret : isNil()") + require.NotNil(t, ret, "ret : isNil()") + assert.Equal(t, ret.Addr, mockServer.Addr, fmt.Sprintf("Addr invalide: %v != %v", ret.Addr, mockServer.Addr)) assert.Equal(t, ret.ReadTimeout, mockServer.ReadTimeout, @@ -111,9 +112,7 @@ func TestNewMainServer(t *testing.T) { assert.Equal(t, ret.WriteTimeout, mockServer.WriteTimeout, fmt.Sprintf("WriteTimeout invalide: %v != %v", ret.WriteTimeout, mockServer.WriteTimeout)) - if ret != nil { - ret.Close() - } + ret.Close() } func TestNewTCPListener(t *testing.T) { @@ -155,13 +154,11 @@ func TestNewAdminServer(t *testing.T) { } ret := newAdminServer(cfg, nil) - assert.NotEqual(t, nil, ret, "ret : isNil()") + require.NotNil(t, ret, "ret : isNil()") assert.Equal(t, mockServer.Addr, ret.Addr, fmt.Sprintf("Addr invalide: %v != %v", ret.Addr, mockServer.Addr)) - if ret != nil { - ret.Close() - } + ret.Close() } func TestRunServer(t *testing.T) { @@ -181,7 +178,6 @@ func TestRunServer(t *testing.T) { } func TestListen(t *testing.T) { - const name = "TestListen" var ( handler, adminHandler http.Handler diff --git a/server/ssl/ssl_test.go b/server/ssl/ssl_test.go index b72fb7ae9a3..e6abdbdcfa7 100644 --- a/server/ssl/ssl_test.go +++ b/server/ssl/ssl_test.go @@ -47,7 +47,7 @@ func TestAppendPEMFileToRootCAPoolFail(t *testing.T) { // In this test we are going to pass a file that does not exist as value of second argument fakeCertificatesFile := "mockcertificates/NO-FILE.pem" - certPool, err := AppendPEMFileToRootCAPool(certPool, fakeCertificatesFile) + _, err := AppendPEMFileToRootCAPool(certPool, fakeCertificatesFile) // Assert AppendPEMFileToRootCAPool correctly throws an error when trying to load an nonexisting file assert.Errorf(t, err, "AppendPEMFileToRootCAPool should throw an error by while loading fake file %s \n", fakeCertificatesFile) diff --git a/static/bidder-info/33across.yaml b/static/bidder-info/33across.yaml index 902db6b362b..cbbdedb2193 100644 --- a/static/bidder-info/33across.yaml +++ b/static/bidder-info/33across.yaml @@ -1,4 +1,7 @@ endpoint: "https://ssc.33across.com/api/v1/s2s" +# This bidder does not operate globally. Please consider setting "disabled: true" in European datacenters. +geoscope: + - "!EEA" maintainer: email: "headerbidding@33across.com" gvlVendorID: 58 @@ -10,4 +13,4 @@ capabilities: userSync: iframe: url: "https://ssc-cms.33across.com/ps/?m=xch&rt=html&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&ru={{.RedirectURL}}&id=zzz000000000002zzz" - userMacro: "33XUSERID33X" \ No newline at end of file + userMacro: "33XUSERID33X" diff --git a/static/bidder-info/adelement.yaml b/static/bidder-info/adelement.yaml new file mode 100644 index 00000000000..3b852283c02 --- /dev/null +++ b/static/bidder-info/adelement.yaml @@ -0,0 +1,18 @@ +endpoint: "https://amp.adelement.com/openrtb2/auction?supply_id={{.SupplyId}}" +endpointCompression: gzip +maintainer: + email: "tech@adelement.com" +gvlVendorID: 196 +capabilities: + app: + mediaTypes: + - banner + - native + - video + - audio + site: + mediaTypes: + - banner + - native + - video + - audio \ No newline at end of file diff --git a/static/bidder-info/adf.yaml b/static/bidder-info/adf.yaml index 2310f346c25..d7d9eccf986 100644 --- a/static/bidder-info/adf.yaml +++ b/static/bidder-info/adf.yaml @@ -1,4 +1,12 @@ +# Please uncomment the appropriate endpoint URL for your datacenter +# Europe endpoint: "https://adx.adform.net/adx/openrtb" +# North/South America +# endpoint: "https://adx2.adform.net/adx/openrtb" +# APAC +# endpoint: "https://adx3.adform.net/adx/openrtb" +geoscope: + - global maintainer: email: "scope.sspp@adform.com" gvlVendorID: 50 diff --git a/static/bidder-info/adkernel.yaml b/static/bidder-info/adkernel.yaml index 1afbdc8d590..22be7bee809 100644 --- a/static/bidder-info/adkernel.yaml +++ b/static/bidder-info/adkernel.yaml @@ -1,15 +1,20 @@ -endpoint: "https://pbs.adksrv.com/hb?zone={{.ZoneID}}" +endpoint: "http://pbs.adksrv.com/hb?zone={{.ZoneID}}" maintainer: email: "prebid-dev@adkernel.com" gvlVendorID: 14 capabilities: app: mediaTypes: - - banner + - banner + - video + - audio + - native site: mediaTypes: - banner - video + - audio + - native userSync: redirect: url: "https://sync.adkernel.com/user-sync?t=image&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&r={{.RedirectURL}}" diff --git a/static/bidder-info/admatic.yaml b/static/bidder-info/admatic.yaml new file mode 100644 index 00000000000..a63e910b3fe --- /dev/null +++ b/static/bidder-info/admatic.yaml @@ -0,0 +1,20 @@ +endpoint: "http://pbs.admatic.com.tr?host={{.Host}}" +maintainer: + email: "prebid@admatic.com.tr" +gvlVendorID: 1281 +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native +userSync: + # admatic supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - iframe diff --git a/static/bidder-info/adprime.yaml b/static/bidder-info/adprime.yaml index 2aab94c6b9d..79d938ad264 100644 --- a/static/bidder-info/adprime.yaml +++ b/static/bidder-info/adprime.yaml @@ -12,3 +12,10 @@ capabilities: - banner - video - native +userSync: + redirect: + url: "https://sync.adprime.com/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}" + userMacro: "[UID]" + iframe: + url: "https://sync.adprime.com/pbserverIframe?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&pbserverUrl={{.RedirectURL}}" + userMacro: "[UID]" diff --git a/static/bidder-info/adquery.yaml b/static/bidder-info/adquery.yaml index 98b6b0ea432..035bd6fc1cb 100644 --- a/static/bidder-info/adquery.yaml +++ b/static/bidder-info/adquery.yaml @@ -1,16 +1,13 @@ -endpoint: "https://bidder.adquery.io/prebid/bid" +endpoint: https://bidder2.adquery.io/prebid/bid maintainer: email: prebid@adquery.io -#endpointCompression: gzip # disabled because otherwise bidder responds with {data:null} gvlVendorID: 902 capabilities: -# app: # disabled because currently it's only a site, not an app (?) -# mediaTypes: -# - banner site: mediaTypes: - banner userSync: - redirect: - url: https://bidder.adquery.io/prebid/userSync?1=1&gdpr={{.GDPR}}&consent={{.GDPRConsent}}&ccpa_consent={{.USPrivacy}}&redirect={{.RedirectURL}} - userMacro: $UID \ No newline at end of file + iframe: + url: https://api.adquery.io/storage?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&ccpa_consent={{.USPrivacy}}&redirect={{.RedirectURL}} + userMacro: $UID + \ No newline at end of file diff --git a/static/bidder-info/adtonos.yaml b/static/bidder-info/adtonos.yaml new file mode 100644 index 00000000000..37a81372710 --- /dev/null +++ b/static/bidder-info/adtonos.yaml @@ -0,0 +1,24 @@ +endpoint: https://exchange.adtonos.com/bid/{{.PublisherID}} +maintainer: + email: support@adtonos.com +gvlVendorID: 682 +geoscope: + - global +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - audio + # NOTE: This is purely an audio ad exchange - video ads are synthesized from audio ads for compatibility + # with mobile games that only understand video mimetypes. The visual layer is just a placeholder. + - video + site: + mediaTypes: + - audio + dooh: + mediaTypes: + - audio +userSync: + redirect: + url: https://play.adtonos.com/redir?to={{.RedirectURL}} + userMacro: '@UUID@' diff --git a/static/bidder-info/aidem.yaml b/static/bidder-info/aidem.yaml index 35ea898aa8a..70f63526879 100644 --- a/static/bidder-info/aidem.yaml +++ b/static/bidder-info/aidem.yaml @@ -1,4 +1,4 @@ -endpoint: "https://zero.aidemsrv.com/ortb/v2.6/bid/request" +endpoint: "https://zero.aidemsrv.com/ortb/v2.6/bid/request?billing_id={{.PublisherID}}" maintainer: email: prebid@aidem.com modifyingVastXmlAllowed: true @@ -15,5 +15,5 @@ capabilities: - native userSync: redirect: - url: https://gum.aidemsrv.com/prebid_sync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}} + url: https://gum.aidemsrv.com/prebid_sync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}} userMacro: "$UID" diff --git a/static/bidder-info/algorix.yaml b/static/bidder-info/algorix.yaml index 9e6c3738f68..dc5f80a5ebd 100644 --- a/static/bidder-info/algorix.yaml +++ b/static/bidder-info/algorix.yaml @@ -1,6 +1,7 @@ endpoint: "https://{{.Host}}.svr-algorix.com/rtb/sa?sid={{.SourceId}}&token={{.AccountID}}" maintainer: email: "prebid@algorix.co" +gvlVendorID: 1176 capabilities: app: mediaTypes: diff --git a/static/bidder-info/alkimi.yaml b/static/bidder-info/alkimi.yaml new file mode 100644 index 00000000000..2e36aebbcb5 --- /dev/null +++ b/static/bidder-info/alkimi.yaml @@ -0,0 +1,19 @@ +endpoint: https://exchange.alkimi-onboarding.com/server/bid +maintainer: + email: support@alkimi.org +gvlVendorID: 1169 +capabilities: + app: + mediaTypes: + - banner + - video + - audio + site: + mediaTypes: + - banner + - video + - audio +userSync: + redirect: + url: "https://user-sync.alkimi-onboarding.com/ssp-sync?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" + userMacro: "${UID}" diff --git a/static/bidder-info/aso.yaml b/static/bidder-info/aso.yaml new file mode 100644 index 00000000000..429b8441a50 --- /dev/null +++ b/static/bidder-info/aso.yaml @@ -0,0 +1,21 @@ +endpoint: "https://srv.aso1.net/pbs/bidder?zid={{.ZoneID}}" +maintainer: + email: "support@adsrv.org" + +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native +userSync: + # Adserver.Online supports user syncing, but requires configuration by the host.Contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect + - iframe diff --git a/static/bidder-info/axonix.yaml b/static/bidder-info/axonix.yaml index 8bb68a4b77a..9744e064ec8 100644 --- a/static/bidder-info/axonix.yaml +++ b/static/bidder-info/axonix.yaml @@ -14,3 +14,7 @@ capabilities: - banner - video - native +userSync: + redirect: + url: "https://openrtb-us-east-1.axonix.com/syn?redirect={{.RedirectURL}}" + userMacro: xxEMODO_IDxx diff --git a/static/bidder-info/bcmint.yaml b/static/bidder-info/bcmint.yaml new file mode 100644 index 00000000000..9d5a62cc516 --- /dev/null +++ b/static/bidder-info/bcmint.yaml @@ -0,0 +1,10 @@ +aliasOf: aso +endpoint: "https://srv.datacygnal.io/pbs/bidder?zid={{.ZoneID}}" +maintainer: + email: "contact@bcm.ltd" +userSync: + # BCM Int. supports user syncing, but requires configuration by the host.Contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect + - iframe \ No newline at end of file diff --git a/static/bidder-info/bidgency.yaml b/static/bidder-info/bidgency.yaml new file mode 100644 index 00000000000..ad5816dffe7 --- /dev/null +++ b/static/bidder-info/bidgency.yaml @@ -0,0 +1,10 @@ +aliasOf: aso +endpoint: "https://srv.bidgx.com/pbs/bidder?zid={{.ZoneID}}" +maintainer: + email: "aso@bidgency.com" +userSync: + # Bidgency supports user syncing, but requires configuration by the host.Contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect + - iframe \ No newline at end of file diff --git a/static/bidder-info/bidmatic.yaml b/static/bidder-info/bidmatic.yaml new file mode 100644 index 00000000000..19211190033 --- /dev/null +++ b/static/bidder-info/bidmatic.yaml @@ -0,0 +1,18 @@ +endpoint: "http://adapter.bidmatic.io/pbs/ortb" +maintainer: + email: "advertising@bidmatic.io" +gvlVendorID: 1134 +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video +userSync: + # bidmatic supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - iframe diff --git a/static/bidder-info/bigoad.yaml b/static/bidder-info/bigoad.yaml new file mode 100644 index 00000000000..ffe964c9837 --- /dev/null +++ b/static/bidder-info/bigoad.yaml @@ -0,0 +1,26 @@ +endpoint: "https://api.imotech.tech/Ad/GetAdOut?sspid={{.SspId}}" +endpointCompression: gzip +geoscope: + - USA + - RUS + - JPN + - BRA + - KOR + - IDN + - TUR + - SAU + - MEX +maintainer: + email: bigoads-prebid@bigo.sg +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-info/blasto.yaml b/static/bidder-info/blasto.yaml new file mode 100644 index 00000000000..c9ba51fd1d2 --- /dev/null +++ b/static/bidder-info/blasto.yaml @@ -0,0 +1,23 @@ +# Contact support@blasto.ai to connect with Blasto exchange. +# We have the following regional endpoint sub-domains: +# US East: t-us +# EU: t-eu +# APAC: t-apac +# Please deploy this config in each of your datacenters with the appropriate regional subdomain +endpoint: 'http://t-us.blasto.ai/bid?rtb_seat_id={{.SourceId}}&secret_key={{.AccountID}}' +maintainer: + email: 'support@blasto.ai' +geoscope: + - global +endpointCompression: 'GZIP' +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-info/bliink.yaml b/static/bidder-info/bliink.yaml index bcdacc42f07..4650e9f8441 100644 --- a/static/bidder-info/bliink.yaml +++ b/static/bidder-info/bliink.yaml @@ -15,6 +15,9 @@ capabilities: - video - native userSync: + iframe: + url: "https://tag.bliink.io/usersync.html?gdpr={{.GDPR}}&gdprConsent={{.GDPRConsent}}&uspConsent={{.USPrivacy}}&redirect={{.RedirectURL}}" + userMacro: "$UID" redirect: url: "https://cookiesync.api.bliink.io/getuid?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}" userMacro: "$UID" diff --git a/static/bidder-info/bluesea.yaml b/static/bidder-info/bluesea.yaml index 14667cafd6e..a9a5ca203d6 100644 --- a/static/bidder-info/bluesea.yaml +++ b/static/bidder-info/bluesea.yaml @@ -3,9 +3,15 @@ maintainer: email: prebid@blueseasx.com endpointCompression: gzip modifyingVastXmlAllowed: true +gvlVendorID: 1294 capabilities: app: mediaTypes: - banner - native - video + site: + mediaTypes: + - banner + - native + - video diff --git a/static/bidder-info/bmtm.yaml b/static/bidder-info/bmtm.yaml index 2992d0fbc7b..27452195479 100644 --- a/static/bidder-info/bmtm.yaml +++ b/static/bidder-info/bmtm.yaml @@ -1,8 +1,12 @@ endpoint: "https://one.elitebidder.com/api/pbs" maintainer: - email: dev@brightmountainmedia.com + email: product@brightmountainmedia.com modifyingVastXmlAllowed: false capabilities: + app: + mediaTypes: + - banner + - video site: mediaTypes: - banner diff --git a/static/bidder-info/boldwin.yaml b/static/bidder-info/boldwin.yaml index 39c1caf34f8..197abb15bc1 100644 --- a/static/bidder-info/boldwin.yaml +++ b/static/bidder-info/boldwin.yaml @@ -1,6 +1,6 @@ endpoint: "http://ssp.videowalldirect.com/pserver" maintainer: - email: "wls_demo_box@smartyads.com" + email: "info@bold-win.com" capabilities: site: mediaTypes: @@ -13,3 +13,7 @@ capabilities: - banner - video - native +userSync: + redirect: + url: "https://sync.videowalldirect.com/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: "[UID]" \ No newline at end of file diff --git a/static/bidder-info/bwx.yaml b/static/bidder-info/bwx.yaml new file mode 100644 index 00000000000..cf716c3890d --- /dev/null +++ b/static/bidder-info/bwx.yaml @@ -0,0 +1,19 @@ +endpoint: "http://rtb.boldwin.live?pid={{.SourceId}}&host={{.Host}}&pbs=1" +maintainer: + email: "prebid@bold-win.com" +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native +userSync: + # BoldwinX supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect diff --git a/static/bidder-info/cointraffic.yaml b/static/bidder-info/cointraffic.yaml new file mode 100644 index 00000000000..d1139ee517d --- /dev/null +++ b/static/bidder-info/cointraffic.yaml @@ -0,0 +1,10 @@ +endpoint: "https://apps.adsgravity.io/pbs/v1/request" +maintainer: + email: tech@cointraffic.io +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner diff --git a/static/bidder-info/concert.yaml b/static/bidder-info/concert.yaml new file mode 100644 index 00000000000..8251518f29a --- /dev/null +++ b/static/bidder-info/concert.yaml @@ -0,0 +1,18 @@ +endpoint: "https://bids.concert.io/bids/openrtb" +endpointCompression: gzip +geoscope: + - global +maintainer: + email: support@concert.io +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + - audio + site: + mediaTypes: + - banner + - video + - audio \ No newline at end of file diff --git a/static/bidder-info/connectad.yaml b/static/bidder-info/connectad.yaml index d9bf283c1ac..486ec289301 100644 --- a/static/bidder-info/connectad.yaml +++ b/static/bidder-info/connectad.yaml @@ -1,16 +1,29 @@ +# Please uncomment the appropriate endpoint URL for your datacenter +# Europe endpoint: "http://bidder.connectad.io/API?src=pbs" +# North/South America +# endpoint: "http://bidder-us.connectad.io/API?src=pbs" +# APAC +# endpoint: "http://bidder-apac.connectad.io/API?src=pbs" +geoscope: + - global maintainer: email: "support@connectad.io" +endpointCompression: gzip gvlVendorID: 138 capabilities: app: mediaTypes: - - banner + - banner site: mediaTypes: - - banner + - banner userSync: - iframe: - url: "https://cdn.connectad.io/connectmyusers.php?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}" + redirect: + url: "https://sync.connectad.io/ImageSyncer?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&cb={{.RedirectURL}}" userMacro: "" # connectad appends the user id to end of the redirect url and does not utilize a macro + iframe: + url: "https://sync.connectad.io/iFrameSyncer?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&cb={{.RedirectURL}}" + userMacro: "" + # connectad appends the user id to end of the redirect url and does not utilize a macro \ No newline at end of file diff --git a/static/bidder-info/consumable.yaml b/static/bidder-info/consumable.yaml index cc290149be2..0f240e87942 100644 --- a/static/bidder-info/consumable.yaml +++ b/static/bidder-info/consumable.yaml @@ -2,10 +2,13 @@ endpoint: "https://e.serverbid.com/api/v2" maintainer: email: "prebid@consumable.com" gvlVendorID: 591 +endpointCompression: gzip capabilities: app: mediaTypes: - banner + - video + - audio site: mediaTypes: - banner diff --git a/static/bidder-info/conversant.yaml b/static/bidder-info/conversant.yaml index 7ffd2761fde..8bf426befcb 100644 --- a/static/bidder-info/conversant.yaml +++ b/static/bidder-info/conversant.yaml @@ -1,6 +1,8 @@ -endpoint: "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb/25" +endpoint: "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb" +openrtb: + version: 2.6 maintainer: - email: "PublisherIntegration@epsilon.com" + email: "PublisherIntegration@epsilon.com" gvlVendorID: 24 capabilities: app: @@ -15,4 +17,4 @@ userSync: redirect: url: "https://prebid-match.dotomi.com/match/bounce/current?version=1&networkId=72582&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&rurl={{.RedirectURL}}" userMacro: "" - # epsilon appends the user id to end of the redirect url and does not utilize a macro + # epsilon appends the user id to end of the redirect url and does not utilize a macro diff --git a/static/bidder-info/copper6ssp.yaml b/static/bidder-info/copper6ssp.yaml new file mode 100644 index 00000000000..d1b3186dc2d --- /dev/null +++ b/static/bidder-info/copper6ssp.yaml @@ -0,0 +1,22 @@ +endpoint: "https://endpoint.copper6.com/" +gvlVendorID: 1356 +maintainer: + email: "info@copper6.com" +capabilities: + site: + mediaTypes: + - banner + - video + - native + app: + mediaTypes: + - banner + - video + - native +userSync: + redirect: + url: "https://csync.copper6.com/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}" + userMacro: "[UID]" + iframe: + url: "https://csync.copper6.com/pbserverIframe?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&pbserverUrl={{.RedirectURL}}" + userMacro: "[UID]" diff --git a/static/bidder-info/cpmstar.yaml b/static/bidder-info/cpmstar.yaml index 7c54ba53ca2..82cb0d0632d 100644 --- a/static/bidder-info/cpmstar.yaml +++ b/static/bidder-info/cpmstar.yaml @@ -1,6 +1,7 @@ endpoint: "https://server.cpmstar.com/openrtbbidrq.aspx" maintainer: email: "prebid@cpmstar.com" +gvlVendorID: 1317 capabilities: app: mediaTypes: @@ -11,6 +12,9 @@ capabilities: - banner - video userSync: + iframe: + url: "https://server.cpmstar.com/usersync.aspx?ifr=1&gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" + userMacro: "$UID" redirect: url: "https://server.cpmstar.com/usersync.aspx?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" - userMacro: "$UID" + userMacro: "$UID" \ No newline at end of file diff --git a/static/bidder-info/criteo.yaml b/static/bidder-info/criteo.yaml index 7d58c68d198..0695f0cc134 100644 --- a/static/bidder-info/criteo.yaml +++ b/static/bidder-info/criteo.yaml @@ -7,16 +7,18 @@ capabilities: mediaTypes: - banner - video + - native site: mediaTypes: - banner - video + - native userSync: # criteo supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. redirect: - url: "https://ssp-sync.criteo.com/user-sync/redirect?gdprapplies={{.GDPR}}&gdpr={{.GDPRConsent}}&ccpa={{.USPrivacy}}&redir={{.RedirectURL}}&profile=230" + url: "https://ssp-sync.criteo.com/user-sync/redirect?gdprapplies={{.GDPR}}&gdpr={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}&profile=230" userMacro: "${CRITEO_USER_ID}" iframe: - url: "https://ssp-sync.criteo.com/user-sync/iframe?gdprapplies={{.GDPR}}&gdpr={{.GDPRConsent}}&ccpa={{.USPrivacy}}&redir={{.RedirectURL}}&profile=230" + url: "https://ssp-sync.criteo.com/user-sync/iframe?gdprapplies={{.GDPR}}&gdpr={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}&profile=230" userMacro: "${CRITEO_USER_ID}" \ No newline at end of file diff --git a/static/bidder-info/displayio.yaml b/static/bidder-info/displayio.yaml new file mode 100644 index 00000000000..4b3b28b745a --- /dev/null +++ b/static/bidder-info/displayio.yaml @@ -0,0 +1,16 @@ +endpoint: "https://prebid.display.io/?publisher={{.PublisherID}}" +endpointCompression: gzip +geoscope: + - global +maintainer: + email: contact@display.io +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video \ No newline at end of file diff --git a/static/bidder-info/driftpixel.yaml b/static/bidder-info/driftpixel.yaml new file mode 100644 index 00000000000..b97451a0d1f --- /dev/null +++ b/static/bidder-info/driftpixel.yaml @@ -0,0 +1,18 @@ +endpoint: "http://rtb.driftpixel.live?pid={{.SourceId}}&host={{.Host}}&pbs=1" +maintainer: + email: "developer@driftpixel.ai" +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native +userSync: + redirect: + url: "https://sync.driftpixel.live/psync?t=s&e=0&gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}" + userMacro: "%USER_ID%" diff --git a/static/bidder-info/dxkulture.yaml b/static/bidder-info/dxkulture.yaml index 1af72e9fc33..d6f03044fda 100644 --- a/static/bidder-info/dxkulture.yaml +++ b/static/bidder-info/dxkulture.yaml @@ -1,6 +1,6 @@ -endpoint: "https://ads.kulture.media/pbs" +endpoint: "https://ads.dxkulture.com/pbs" maintainer: - email: "devops@kulture.media" + email: "devops@dxkulture.com" capabilities: app: mediaTypes: @@ -12,5 +12,5 @@ capabilities: - video userSync: redirect: - url: "https://ads.kulture.media/usync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}" + url: "https://ads.dxkulture.com/usync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}" userMacro: "$UID" diff --git a/static/bidder-info/e_volution.yaml b/static/bidder-info/e_volution.yaml index b9d5532ca04..aa33e4660d1 100644 --- a/static/bidder-info/e_volution.yaml +++ b/static/bidder-info/e_volution.yaml @@ -17,4 +17,6 @@ userSync: redirect: url: "https://sync.e-volution.ai/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&redirect={{.RedirectURL}}" userMacro: "[UID]" - + iframe: + url: "https://sync.e-volution.ai/pbserverIframe?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&pbserverUrl={{.RedirectURL}}" + userMacro: "[UID]" diff --git a/static/bidder-info/embimedia.yaml b/static/bidder-info/embimedia.yaml new file mode 100644 index 00000000000..9cfa1546bbe --- /dev/null +++ b/static/bidder-info/embimedia.yaml @@ -0,0 +1,2 @@ +endpoint: "http://ads-pbs.bidder-embi.media/openrtb/{{.PublisherID}}?host={{.Host}}" +aliasOf: "limelightDigital" diff --git a/static/bidder-info/epsilon.yaml b/static/bidder-info/epsilon.yaml index 856d82a6f3f..cb8318102e8 100644 --- a/static/bidder-info/epsilon.yaml +++ b/static/bidder-info/epsilon.yaml @@ -1,18 +1,6 @@ -endpoint: "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb/25" -maintainer: - email: "PublisherIntegration@epsilon.com" -gvlVendorID: 24 -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video +aliasOf: conversant userSync: redirect: - url: "https://prebid-match.dotomi.com/match/bounce/current?version=1&networkId=72582&rurl={{.RedirectURL}}" + url: "https://prebid-match.dotomi.com/match/bounce/current?version=1&networkId=72582&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&rurl={{.RedirectURL}}" userMacro: "" # epsilon/conversant appends the user id to end of the redirect url and does not utilize a macro diff --git a/static/bidder-info/escalax.yaml b/static/bidder-info/escalax.yaml new file mode 100644 index 00000000000..8d6871182ce --- /dev/null +++ b/static/bidder-info/escalax.yaml @@ -0,0 +1,18 @@ +endpoint: 'http://bidder_us.escalax.io/?partner={{.SourceId}}&token={{.AccountID}}&type=pbs' +maintainer: + email: 'connect@escalax.io' +geoscope: + - global +endpointCompression: "GZIP" +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-info/felixads.yaml b/static/bidder-info/felixads.yaml new file mode 100644 index 00000000000..98a8745a881 --- /dev/null +++ b/static/bidder-info/felixads.yaml @@ -0,0 +1,2 @@ +endpoint: "http://felixads-prebid.smart-hub.io/pbserver/?seat={{.AccountID}}&token={{.SourceId}}" +aliasOf: "smarthub" diff --git a/static/bidder-info/filmzie.yaml b/static/bidder-info/filmzie.yaml new file mode 100644 index 00000000000..29d2039ee3f --- /dev/null +++ b/static/bidder-info/filmzie.yaml @@ -0,0 +1 @@ +aliasOf: "limelightDigital" diff --git a/static/bidder-info/finative.yaml b/static/bidder-info/finative.yaml new file mode 100644 index 00000000000..e1136c3220d --- /dev/null +++ b/static/bidder-info/finative.yaml @@ -0,0 +1,2 @@ +endpoint: "https://b.finative.cloud/cds/rtb/bid?ssp={{.AccountID}}" +aliasOf: "seedingAlliance" \ No newline at end of file diff --git a/static/bidder-info/freewheel-ssp.yaml b/static/bidder-info/freewheel-ssp.yaml index 0c0a11edfce..43c1ca166a2 100644 --- a/static/bidder-info/freewheel-ssp.yaml +++ b/static/bidder-info/freewheel-ssp.yaml @@ -1,16 +1,5 @@ -endpoint: "https://ads.stickyadstv.com/openrtb/dsp" -maintainer: - email: prebid-maintainer@freewheel.com -gvlVendorID: 285 -modifyingVastXmlAllowed: true -capabilities: - app: - mediaTypes: - - video - site: - mediaTypes: - - video +aliasOf: freewheelssp userSync: iframe: url: "https://ads.stickyadstv.com/pbs-user-sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&r={{.RedirectURL}}" - userMacro: "{viewerid}" + userMacro: "{viewerid}" \ No newline at end of file diff --git a/static/bidder-info/freewheelssp.yaml b/static/bidder-info/freewheelssp.yaml index 8c9286cbbc0..cd18c2d8172 100644 --- a/static/bidder-info/freewheelssp.yaml +++ b/static/bidder-info/freewheelssp.yaml @@ -13,4 +13,7 @@ capabilities: userSync: iframe: url: "https://ads.stickyadstv.com/pbs-user-sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&r={{.RedirectURL}}" - userMacro: "{viewerid}" \ No newline at end of file + userMacro: "{viewerid}" +openrtb: + version: 2.6 + gpp-supported: true \ No newline at end of file diff --git a/static/bidder-info/grid.yaml b/static/bidder-info/grid.yaml index bf1832c9590..9d9e7aa58f5 100644 --- a/static/bidder-info/grid.yaml +++ b/static/bidder-info/grid.yaml @@ -15,5 +15,5 @@ capabilities: - native userSync: redirect: - url: "https://x.bidswitch.net/check_uuid/{{.RedirectURL}}?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}" + url: "https://x.bidswitch.net/check_uuid/{{.RedirectURL}}?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&us_privacy={{.USPrivacy}}" userMacro: "${BSW_UUID}" diff --git a/static/bidder-info/gumgum.yaml b/static/bidder-info/gumgum.yaml index f7e782e40df..945acf7ca5c 100644 --- a/static/bidder-info/gumgum.yaml +++ b/static/bidder-info/gumgum.yaml @@ -12,3 +12,6 @@ userSync: url: "https://rtb.gumgum.com/usync/prbds2s?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" userMacro: "" # gumgum appends the user id to end of the redirect url and does not utilize a macro +openrtb: + version: 2.6 + gpp-supported: true \ No newline at end of file diff --git a/static/bidder-info/imds.yaml b/static/bidder-info/imds.yaml index 491a5bd0ac6..135b67fc1f9 100644 --- a/static/bidder-info/imds.yaml +++ b/static/bidder-info/imds.yaml @@ -1,4 +1,8 @@ endpoint: "https://pbs.technoratimedia.com/openrtb/bids/{{.AccountID}}?src={{.SourceId}}&adapter=imds" +# This bidder does not operate globally. Please consider setting "disabled: true" outside of the following regions: +geoscope: + - USA + - CAN maintainer: email: "eng-demand@imds.tv" capabilities: diff --git a/static/bidder-info/indicue.yaml b/static/bidder-info/indicue.yaml new file mode 100644 index 00000000000..1353424dfed --- /dev/null +++ b/static/bidder-info/indicue.yaml @@ -0,0 +1,9 @@ +aliasOf: adtelligent +endpoint: "http://ghb.console.indicue.com/pbs/ortb" +maintainer: + email: "ops@indicue.com" +userSync: + # Indicue ssp supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - iframe diff --git a/static/bidder-info/inmobi.yaml b/static/bidder-info/inmobi.yaml index 72bdd21d974..3f0c9b9b480 100644 --- a/static/bidder-info/inmobi.yaml +++ b/static/bidder-info/inmobi.yaml @@ -12,8 +12,9 @@ capabilities: mediaTypes: - banner - video + - native userSync: - redirect: - url: "https://sync.inmobi.com/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" + iframe: + url: "https://sync.inmobi.com/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redirect={{.RedirectURL}}" userMacro: "{ID5UID}" diff --git a/static/bidder-info/iqx.yaml b/static/bidder-info/iqx.yaml index ab10ad96289..3569c3accf8 100644 --- a/static/bidder-info/iqx.yaml +++ b/static/bidder-info/iqx.yaml @@ -1,6 +1,6 @@ endpoint: "http://rtb.iqzone.com?pid={{.SourceId}}&host={{.Host}}&pbs=1" maintainer: - email: "it@iqzone.com" + email: "adops@iqzone.com" capabilities: app: mediaTypes: @@ -16,4 +16,4 @@ userSync: # IQX supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. supports: - - redirect \ No newline at end of file + - redirect diff --git a/static/bidder-info/iqzone.yaml b/static/bidder-info/iqzone.yaml index 3465cfabcb0..1b6c0963ab0 100644 --- a/static/bidder-info/iqzone.yaml +++ b/static/bidder-info/iqzone.yaml @@ -1,6 +1,6 @@ endpoint: "http://smartssp-us-east.iqzone.com/pserver" maintainer: - email: "smartssp@iqzone.com" + email: "adops@iqzone.com" capabilities: site: mediaTypes: @@ -13,4 +13,10 @@ capabilities: - banner - video - native - +userSync: + redirect: + url: "https://cs.iqzone.com/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}" + userMacro: "[UID]" + iframe: + url: "https://cs.iqzone.com/pbserverIframe?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&pbserverUrl={{.RedirectURL}}" + userMacro: "[UID]" \ No newline at end of file diff --git a/static/bidder-info/ix.yaml b/static/bidder-info/ix.yaml index 4ab520ad81c..398865b9a13 100644 --- a/static/bidder-info/ix.yaml +++ b/static/bidder-info/ix.yaml @@ -23,4 +23,5 @@ userSync: url: "https://ssum-sec.casalemedia.com/usermatch?s=184674&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gppsid={{.GPPSID}}&cb={{.RedirectURL}}" # ix appends the user id to end of the redirect url and does not utilize a macro openrtb: + version: 2.6 gpp-supported: true diff --git a/static/bidder-info/jdpmedia.yaml b/static/bidder-info/jdpmedia.yaml new file mode 100644 index 00000000000..8139dd4ffb4 --- /dev/null +++ b/static/bidder-info/jdpmedia.yaml @@ -0,0 +1,5 @@ +endpoint: "http://jdpmedia-prebid.smart-hub.io/pbserver/?seat={{.AccountID}}&token={{.SourceId}}" +aliasOf: "smarthub" +userSync: + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-info/kargo.yaml b/static/bidder-info/kargo.yaml index e73eabe9cdf..6acd679ebc0 100644 --- a/static/bidder-info/kargo.yaml +++ b/static/bidder-info/kargo.yaml @@ -11,6 +11,9 @@ capabilities: - native userSync: redirect: - url: "https://crb.kargo.com/api/v1/dsync/PrebidServer?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" + url: "https://crb.kargo.com/api/v1/dsync/PrebidServer?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&r={{.RedirectURL}}" userMacro: "$UID" -endpointCompression: "GZIP" \ No newline at end of file +endpointCompression: "GZIP" +openrtb: + version: 2.6 + gpp-supported: true diff --git a/static/bidder-info/krushmedia.yaml b/static/bidder-info/krushmedia.yaml index 75d29b5ebe7..aa28b473ce5 100644 --- a/static/bidder-info/krushmedia.yaml +++ b/static/bidder-info/krushmedia.yaml @@ -14,5 +14,8 @@ capabilities: - native userSync: redirect: - url: "https://cs.krushmedia.com/4e4abdd5ecc661643458a730b1aa927d.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + url: "https://cs.krushmedia.com/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}" + userMacro: "[UID]" + iframe: + url: "https://cs.krushmedia.com/pbserverIframe?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&pbserverUrl={{.RedirectURL}}" userMacro: "[UID]" diff --git a/static/bidder-info/lemmadigital.yaml b/static/bidder-info/lemmadigital.yaml index 535c91ffa77..03cabc8f710 100644 --- a/static/bidder-info/lemmadigital.yaml +++ b/static/bidder-info/lemmadigital.yaml @@ -1,4 +1,4 @@ -endpoint: "https://sg.ads.lemmatechnologies.com/lemma/servad?pid={{.PublisherID}}&aid={{.AdUnit}}" +endpoint: "https://pbid.lemmamedia.com/lemma/servad?src=prebid&pid={{.PublisherID}}&aid={{.AdUnit}}" maintainer: email: support@lemmatechnologies.com endpointCompression: gzip @@ -11,4 +11,8 @@ capabilities: site: mediaTypes: - banner - - video \ No newline at end of file + - video +userSync: + iframe: + url: "https://sync.lemmadigital.com/setuid?publisher=850&redirect={{.RedirectURL}}" + userMacro: "${UUID}" \ No newline at end of file diff --git a/static/bidder-info/lockerdome.yaml b/static/bidder-info/lockerdome.yaml index cfefb2f995b..530c54eaaff 100644 --- a/static/bidder-info/lockerdome.yaml +++ b/static/bidder-info/lockerdome.yaml @@ -1,4 +1,7 @@ endpoint: "https://lockerdome.com/ladbid/prebidserver/openrtb2" +# This bidder does not operate globally. Please consider setting "disabled: true" outside of the following regions: +geoscope: + - USA maintainer: email: "bidding@lockerdome.com" capabilities: diff --git a/static/bidder-info/loyal.yaml b/static/bidder-info/loyal.yaml new file mode 100644 index 00000000000..f4c195399cb --- /dev/null +++ b/static/bidder-info/loyal.yaml @@ -0,0 +1,16 @@ +endpoint: "https://us-east-1.loyal.app/pserver" +geoscope: + - USA +maintainer: + email: "hello@loyal.app" +capabilities: + site: + mediaTypes: + - banner + - video + - native + app: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-info/lunamedia.yaml b/static/bidder-info/lunamedia.yaml index 4064a566040..6f450382c0e 100644 --- a/static/bidder-info/lunamedia.yaml +++ b/static/bidder-info/lunamedia.yaml @@ -1,4 +1,6 @@ endpoint: "http://rtb.lunamedia.live/?pid={{.PublisherID}}" +geoscope: + - USA maintainer: email: "cs@lunamedia.io" capabilities: diff --git a/static/bidder-info/mabidder.yaml b/static/bidder-info/mabidder.yaml index 3f03fd87854..b66e9a8bab6 100644 --- a/static/bidder-info/mabidder.yaml +++ b/static/bidder-info/mabidder.yaml @@ -8,4 +8,7 @@ capabilities: - banner site: mediaTypes: - - banner \ No newline at end of file + - banner +# This bidder does not operate globally. Please consider setting "disabled: true" outside of the following regions: +geoscope: + - CAN \ No newline at end of file diff --git a/static/bidder-info/magnite.yaml b/static/bidder-info/magnite.yaml new file mode 100644 index 00000000000..109c20d03bf --- /dev/null +++ b/static/bidder-info/magnite.yaml @@ -0,0 +1 @@ +aliasOf: "rubicon" \ No newline at end of file diff --git a/static/bidder-info/markapp.yaml b/static/bidder-info/markapp.yaml new file mode 100644 index 00000000000..79d8887c20c --- /dev/null +++ b/static/bidder-info/markapp.yaml @@ -0,0 +1,5 @@ +endpoint: "http://markapp-prebid.smart-hub.io/pbserver/?seat={{.AccountID}}&token={{.SourceId}}" +aliasOf: "smarthub" +userSync: + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-info/mediago.yaml b/static/bidder-info/mediago.yaml new file mode 100644 index 00000000000..78b03e2595a --- /dev/null +++ b/static/bidder-info/mediago.yaml @@ -0,0 +1,26 @@ +# Contact ext_mediago_cm@baidu.com to ask about enabling a connection to the MediaGo DSP. +# We have the following regional endpoint domains: rtb-us, rtb-eu, rtb-jp +# Please deploy this config in each of your datacenters with the appropriate regional subdomain +endpoint: "https://REGION.mediago.io/api/bid?tn={{.AccountID}}" +endpointCompression: gzip +geoscope: + - USA + - DEU + - JPN + - GBR + - KOR + - CAN + - FRA + - ITA +maintainer: + email: ext_mediago_cm@baidu.com +gvlVendorID: 1020 +capabilities: + site: + mediaTypes: + - banner + - native +userSync: + redirect: + url: https://trace.mediago.io/ju/cs/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}} + userMacro: $UID \ No newline at end of file diff --git a/static/bidder-info/medianet.yaml b/static/bidder-info/medianet.yaml index ea47de2b11d..1b23d5aec96 100644 --- a/static/bidder-info/medianet.yaml +++ b/static/bidder-info/medianet.yaml @@ -3,6 +3,9 @@ extra_info: "https://medianet.golang.pbs.com" maintainer: email: "prebid-support@media.net" gvlVendorID: 142 +endpointCompression: gzip +openrtb: + version: 2.6 modifyingVastXmlAllowed: true capabilities: app: @@ -17,5 +20,5 @@ capabilities: - native userSync: redirect: - url: https://hbx.media.net/cksync.php?cs=1&type=pbs&ovsid=setstatuscode&bidder=medianet&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}} + url: https://hbx.media.net/cksync.php?cs=1&type=pbs&ovsid=setstatuscode&bidder=medianet&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}} userMacro: "" \ No newline at end of file diff --git a/static/bidder-info/melozen.yaml b/static/bidder-info/melozen.yaml new file mode 100644 index 00000000000..391e0a8d43b --- /dev/null +++ b/static/bidder-info/melozen.yaml @@ -0,0 +1,19 @@ +# We have the following regional endpoint domains: us-east and us-west +# Please deploy this config in each of your datacenters with the appropriate regional subdomain +endpoint: "https://prebid.melozen.com/rtb/v2/bid?publisher_id={{.PublisherID}}" +endpointCompression: gzip +geoscope: + - global +maintainer: + email: DSP@melodong.com +capabilities: + site: + mediaTypes: + - banner + - video + - native + app: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-info/metax.yaml b/static/bidder-info/metax.yaml new file mode 100644 index 00000000000..d9867322d01 --- /dev/null +++ b/static/bidder-info/metax.yaml @@ -0,0 +1,18 @@ +# The MetaX Bidding adapter requires setup before beginning. Please contact us at +endpoint: "https://hb.metaxads.com/prebid?sid={{.PublisherID}}&adunit={{.AdUnit}}&source=prebid-server" +maintainer: + email: "prebid@metaxsoft.com" +gvlVendorID: 1301 +capabilities: + app: + mediaTypes: + - banner + - video + - native + - audio + site: + mediaTypes: + - banner + - video + - native + - audio diff --git a/static/bidder-info/mgidX.yaml b/static/bidder-info/mgidX.yaml index ab783beb560..247aa96f033 100644 --- a/static/bidder-info/mgidX.yaml +++ b/static/bidder-info/mgidX.yaml @@ -1,4 +1,7 @@ -endpoint: "https://us-east-x.mgid.com/pserver" +disabled: true +# We have the following regional endpoint domains: 'us-east-x' and 'eu-x' +# Please deploy this config in each of your datacenters with the appropriate regional subdomain +endpoint: "https://REGION.mgid.com/pserver" maintainer: email: "prebid@mgid.com" gvlVendorID: 358 @@ -8,7 +11,6 @@ capabilities: - banner - video - native - app: mediaTypes: - banner diff --git a/static/bidder-info/minutemedia.yaml b/static/bidder-info/minutemedia.yaml new file mode 100644 index 00000000000..55f5a2b7cd3 --- /dev/null +++ b/static/bidder-info/minutemedia.yaml @@ -0,0 +1,18 @@ +endpoint: "https://pbs.minutemedia-prebid.com/pbs-mm" +maintainer: + email: hb@minutemedia.com +gvlVendorID: 918 +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video +userSync: + iframe: + url: https://pbs-cs.minutemedia-prebid.com/pbs-iframe?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redirect={{.RedirectURL}} + userMacro: "[PBS_UID]" diff --git a/static/bidder-info/missena.yaml b/static/bidder-info/missena.yaml new file mode 100644 index 00000000000..47f089b9c5a --- /dev/null +++ b/static/bidder-info/missena.yaml @@ -0,0 +1,16 @@ +endpoint: https://bid.missena.io/ +maintainer: + email: prebid@missena.com +gvlVendorID: 687 +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner +userSync: + iframe: + url: https://sync.missena.io/iframe?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}} + userMacro: $UID \ No newline at end of file diff --git a/static/bidder-info/mobfoxpb.yaml b/static/bidder-info/mobfoxpb.yaml index 0a1e78ec23c..cfd548b565a 100644 --- a/static/bidder-info/mobfoxpb.yaml +++ b/static/bidder-info/mobfoxpb.yaml @@ -1,6 +1,6 @@ endpoint: "http://bes.mobfox.com/?c=__route__&m=__method__&key=__key__" maintainer: - email: "platform@mobfox.com" + email: "support@mobfox.com" capabilities: app: mediaTypes: diff --git a/static/bidder-info/mobilefuse.yaml b/static/bidder-info/mobilefuse.yaml index e1474f775fc..62714f15124 100644 --- a/static/bidder-info/mobilefuse.yaml +++ b/static/bidder-info/mobilefuse.yaml @@ -1,4 +1,8 @@ endpoint: "http://mfx.mobilefuse.com/openrtb?pub_id={{.PublisherID}}" +# This bidder does not operate globally. Please consider setting "disabled: true" outside of the following regions: +geoscope: + - USA + - CAN maintainer: email: prebid@mobilefuse.com gvlVendorID: 909 @@ -8,4 +12,7 @@ capabilities: - banner - video - native -endpointCompression: "GZIP" \ No newline at end of file +endpointCompression: "GZIP" +openrtb: + version: 2.6 + gpp-supported: true diff --git a/static/bidder-info/nativo.yaml b/static/bidder-info/nativo.yaml new file mode 100644 index 00000000000..308fc6388db --- /dev/null +++ b/static/bidder-info/nativo.yaml @@ -0,0 +1,22 @@ +endpoint: "https://exchange.postrelease.com/esi?ntv_epid=7" +maintainer: + email: "prebiddev@nativo.com" +gvlVendorID: 263 +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native +userSync: + redirect: + url: https://jadserve.postrelease.com/suid/101787?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&ntv_gpp_consent={{.GPP}}&ntv_r={{.RedirectURL}} + userMacro: NTV_USER_ID +openrtb: + version: 2.6 + gpp-supported: true \ No newline at end of file diff --git a/static/bidder-info/nextmillennium.yaml b/static/bidder-info/nextmillennium.yaml index a6712846a41..11182ac271c 100644 --- a/static/bidder-info/nextmillennium.yaml +++ b/static/bidder-info/nextmillennium.yaml @@ -1,13 +1,16 @@ endpoint: "https://pbs.nextmillmedia.com/openrtb2/auction" maintainer: email: "accountmanagers@nextmillennium.io" +gvlVendorID: 1060 capabilities: app: mediaTypes: - banner + - video site: mediaTypes: - banner + - video userSync: iframe: url: "https://cookies.nextmillmedia.com/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" diff --git a/static/bidder-info/nobid.yaml b/static/bidder-info/nobid.yaml index 7c33998bf20..6e742a18e49 100644 --- a/static/bidder-info/nobid.yaml +++ b/static/bidder-info/nobid.yaml @@ -15,3 +15,6 @@ userSync: redirect: url: "https://ads.servenobid.com/getsync?tek=pbs&ver=1&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" userMacro: "$UID" + iframe: + url: "https://public.servenobid.com/sync.html?tek=pbs&ver=1&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" + userMacro: $UID diff --git a/static/bidder-info/oms.yaml b/static/bidder-info/oms.yaml new file mode 100644 index 00000000000..8bb9299d6e9 --- /dev/null +++ b/static/bidder-info/oms.yaml @@ -0,0 +1,11 @@ +endpoint: "http://rt.marphezis.com/pbs" +maintainer: + email: "prebid@onlinemediasolutions.com" +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner + diff --git a/static/bidder-info/onetag.yaml b/static/bidder-info/onetag.yaml index bc52faffe1b..00b835269d2 100644 --- a/static/bidder-info/onetag.yaml +++ b/static/bidder-info/onetag.yaml @@ -19,4 +19,7 @@ userSync: iframe: url: "https://onetag-sys.com/usync/?redir={{.RedirectURL}}&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}" userMacro: "${USER_TOKEN}" -endpointCompression: "GZIP" \ No newline at end of file + redirect: + url: "https://onetag-sys.com/usync/?tag=img&redir={{.RedirectURL}}&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}" + userMacro: "${USER_TOKEN}" +endpointCompression: "GZIP" diff --git a/static/bidder-info/openweb.yaml b/static/bidder-info/openweb.yaml index 9272719cf19..cde54acfb25 100644 --- a/static/bidder-info/openweb.yaml +++ b/static/bidder-info/openweb.yaml @@ -1,7 +1,8 @@ -endpoint: "http://ghb.spotim.market/pbs/ortb" +endpoint: "https://pbs.openwebmp.com/pbs" maintainer: email: "monetization@openweb.com" gvlVendorID: 280 +modifyingVastXmlAllowed: true capabilities: app: mediaTypes: @@ -12,7 +13,6 @@ capabilities: - banner - video userSync: - # openweb supports user syncing, but requires configuration by the host. contact this - # bidder directly at the email address in this file to ask about enabling user sync. - supports: - - redirect + iframe: + url: https://pbs-cs.openwebmp.com/pbs-iframe?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redirect={{.RedirectURL}} + userMacro: "[PBS_UID]" diff --git a/static/bidder-info/openx.yaml b/static/bidder-info/openx.yaml index 9837b5dc92c..d56ae466885 100644 --- a/static/bidder-info/openx.yaml +++ b/static/bidder-info/openx.yaml @@ -2,6 +2,7 @@ endpoint: "http://rtb.openx.net/prebid" maintainer: email: "prebid@openx.com" gvlVendorID: 69 +endpointCompression: gzip capabilities: app: mediaTypes: @@ -19,4 +20,6 @@ userSync: redirect: url: "https://rtb.openx.net/sync/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r={{.RedirectURL}}" userMacro: "${UID}" +openrtb: + version: 2.6 diff --git a/static/bidder-info/oraki.yaml b/static/bidder-info/oraki.yaml new file mode 100644 index 00000000000..d5e767ac540 --- /dev/null +++ b/static/bidder-info/oraki.yaml @@ -0,0 +1,18 @@ +endpoint: "https://eu1.oraki.io/pserver" +maintainer: + email: "prebid@oraki.io" +capabilities: + site: + mediaTypes: + - banner + - video + - native + app: + mediaTypes: + - banner + - video + - native +userSync: + redirect: + url: "https://sync.oraki.io/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}" + userMacro: "[UID]" diff --git a/static/bidder-info/ownadx.yaml b/static/bidder-info/ownadx.yaml index 37567db1144..073d75f0278 100644 --- a/static/bidder-info/ownadx.yaml +++ b/static/bidder-info/ownadx.yaml @@ -1,4 +1,4 @@ -endpoint: "https://pbs.prebid-ownadx.com/bidder/bid/{{.AccountID}}/{{.ZoneID}}?token={{.SourceId}}" +endpoint: "https://pbs.prebid-ownadx.com/bidder/bid/{{.SeatID}}/{{.SspID}}?token={{.TokenID}}" maintainer: email: prebid-team@techbravo.com capabilities: @@ -10,3 +10,9 @@ capabilities: mediaTypes: - banner - video + +userSync: + redirect: + url: "https://sync.spoutroserve.com/user-sync?t=image&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&s3={{.RedirectURL}}" + userMacro: "{USER_ID}" +endpointCompression: "GZIP" \ No newline at end of file diff --git a/static/bidder-info/pgamssp.yaml b/static/bidder-info/pgamssp.yaml index a762d8f15d4..75cec680c13 100644 --- a/static/bidder-info/pgamssp.yaml +++ b/static/bidder-info/pgamssp.yaml @@ -1,4 +1,5 @@ endpoint: "http://us-east.pgammedia.com/pserver" +gvlVendorID: 1353 maintainer: email: "info@pgammedia.com" capabilities: @@ -15,5 +16,5 @@ capabilities: - native userSync: redirect: - url: "https://cs.pgammedia.com/pserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + url: "https://cs.pgammedia.com/pserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}" userMacro: "[UID]" diff --git a/static/bidder-info/playdigo.yaml b/static/bidder-info/playdigo.yaml new file mode 100644 index 00000000000..b90b680f183 --- /dev/null +++ b/static/bidder-info/playdigo.yaml @@ -0,0 +1,24 @@ +endpoint: "https://server.playdigo.com/pserver" +geoscope: + - USA +maintainer: + email: "yr@playdigo.com" +gvlVendorID: 1302 +capabilities: + site: + mediaTypes: + - banner + - video + - native + app: + mediaTypes: + - banner + - video + - native +userSync: + redirect: + url: "https://cs.playdigo.com/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}" + userMacro: "[UID]" + iframe: + url: "https://cs.playdigo.com/pbserverIframe?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&pbserverUrl={{.RedirectURL}}" + userMacro: "[UID]" diff --git a/static/bidder-info/pubmatic.yaml b/static/bidder-info/pubmatic.yaml index 0a8283b4f98..6cb4cc8a5fe 100644 --- a/static/bidder-info/pubmatic.yaml +++ b/static/bidder-info/pubmatic.yaml @@ -22,4 +22,5 @@ userSync: url: "https://image8.pubmatic.com/AdServer/ImgSync?p=159706&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&pu={{.RedirectURL}}" userMacro: "#PMUID" openrtb: + version: 2.6 gpp-supported: true diff --git a/static/bidder-info/pubrise.yaml b/static/bidder-info/pubrise.yaml new file mode 100644 index 00000000000..fe5e6cd6d40 --- /dev/null +++ b/static/bidder-info/pubrise.yaml @@ -0,0 +1,21 @@ +endpoint: "https://backend.pubrise.ai/" +maintainer: + email: "prebid@pubrise.ai" +capabilities: + site: + mediaTypes: + - banner + - video + - native + app: + mediaTypes: + - banner + - video + - native +userSync: + redirect: + url: "https://sync.pubrise.ai/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}" + userMacro: "[UID]" + iframe: + url: "https://sync.pubrise.ai/pbserverIframe?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&pbserverUrl={{.RedirectURL}}" + userMacro: "[UID]" diff --git a/static/bidder-info/pulsepoint.yaml b/static/bidder-info/pulsepoint.yaml index 762dbbb0c73..87aff0b5f04 100644 --- a/static/bidder-info/pulsepoint.yaml +++ b/static/bidder-info/pulsepoint.yaml @@ -19,3 +19,6 @@ userSync: redirect: url: "https://bh.contextweb.com/rtset?pid=561205&ev=1&rurl={{.RedirectURL}}" userMacro: "%%VGUID%%" +openrtb: + version: 2.6 + gpp-supported: true diff --git a/static/bidder-info/qt.yaml b/static/bidder-info/qt.yaml new file mode 100644 index 00000000000..a8d16e574bd --- /dev/null +++ b/static/bidder-info/qt.yaml @@ -0,0 +1,19 @@ +endpoint: "https://endpoint1.qt.io/pserver" +maintainer: + email: "qtssp-support@qt.io" +gvlVendorID: 1331 +capabilities: + site: + mediaTypes: + - banner + - video + - native + app: + mediaTypes: + - banner + - video + - native +userSync: + redirect: + url: "https://cs.qt.io/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}" + userMacro: "[UID]" diff --git a/static/bidder-info/readpeak.yaml b/static/bidder-info/readpeak.yaml new file mode 100644 index 00000000000..df7793383e8 --- /dev/null +++ b/static/bidder-info/readpeak.yaml @@ -0,0 +1,15 @@ +endpoint: https://dsp.readpeak.com/header/prebid +geoscope: + - EEA +maintainer: + email: devteam@readpeak.com +gvlVendorID: 290 +capabilities: + app: + mediaTypes: + - banner + - native + site: + mediaTypes: + - banner + - native diff --git a/static/bidder-info/relevantdigital.yaml b/static/bidder-info/relevantdigital.yaml new file mode 100644 index 00000000000..cc873f2804a --- /dev/null +++ b/static/bidder-info/relevantdigital.yaml @@ -0,0 +1,17 @@ +endpoint: "https://{{.Host}}.relevant-digital.com/openrtb2/auction" +maintainer: + email: "support@relevant-digital.com" +gvlVendorID: 1100 +capabilities: + app: + mediaTypes: + - banner + - video + - native + - audio + site: + mediaTypes: + - banner + - video + - native + - audio diff --git a/static/bidder-info/rise.yaml b/static/bidder-info/rise.yaml index d429e8780a7..79b64d5a4e7 100644 --- a/static/bidder-info/rise.yaml +++ b/static/bidder-info/rise.yaml @@ -2,7 +2,7 @@ endpoint: "https://pbs.yellowblue.io/pbs" maintainer: email: rise-prog-dev@risecodes.com gvlVendorID: 1043 -modifyingVastXmlAllowed: false +modifyingVastXmlAllowed: true capabilities: app: mediaTypes: @@ -14,5 +14,5 @@ capabilities: - video userSync: iframe: - url: https://pbs-cs.yellowblue.io/pbs-iframe?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}} + url: https://pbs-cs.yellowblue.io/pbs-iframe?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redirect={{.RedirectURL}} userMacro: "[PBS_UID]" diff --git a/static/bidder-info/bizzclick.yaml b/static/bidder-info/roulax.yaml similarity index 54% rename from static/bidder-info/bizzclick.yaml rename to static/bidder-info/roulax.yaml index 28ff0d44285..646a9cbc77a 100644 --- a/static/bidder-info/bizzclick.yaml +++ b/static/bidder-info/roulax.yaml @@ -1,6 +1,6 @@ -endpoint: "http://us-e-node1.bizzclick.com/bid?rtb_seat_id={{.SourceId}}&secret_key={{.AccountID}}" +endpoint: "http://dsp.rcoreads.com/api/{{.PublisherID}}?pid={{.AccountID}}" maintainer: - email: "support@bizzclick.com" + email: "bussiness@roulax.io" capabilities: app: mediaTypes: diff --git a/static/bidder-info/rtbhouse.yaml b/static/bidder-info/rtbhouse.yaml index ad2fbfcbc95..fd9df062b15 100644 --- a/static/bidder-info/rtbhouse.yaml +++ b/static/bidder-info/rtbhouse.yaml @@ -1,12 +1,28 @@ +# Contact prebid@rtbhouse.com to ask about enabling a connection to the bidder. +# Please configure the following endpoints for your datacenter +# EMEA endpoint: "http://prebidserver-s2s-ams.creativecdn.com/bidder/prebidserver/bids" +# US East +# endpoint: "http://prebidserver-s2s-ash.creativecdn.com/bidder/prebidserver/bids" +# US West +# endpoint: "http://prebidserver-s2s-phx.creativecdn.com/bidder/prebidserver/bids" +# APAC +# endpoint: "http://prebidserver-s2s-sin.creativecdn.com/bidder/prebidserver/bids" +geoscope: + - global maintainer: email: "prebid@rtbhouse.com" endpointCompression: gzip gvlVendorID: 16 capabilities: + app: + mediaTypes: + - banner + - native site: mediaTypes: - banner + - native userSync: # rtbhouse supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. diff --git a/static/bidder-info/rubicon.yaml b/static/bidder-info/rubicon.yaml index b3de838c9fd..b4c2cfce6d2 100644 --- a/static/bidder-info/rubicon.yaml +++ b/static/bidder-info/rubicon.yaml @@ -1,8 +1,21 @@ -endpoint: "http://exapi-us-east.rubiconproject.com/a/api/exchange.json" +# Contact global-support@magnite.com to ask about enabling a connection to the Magnite (formerly Rubicon) exchange. +# We have the following regional endpoint domains: exapi-us-east, exapi-us-west, exapi-apac, exapi-eu +# Please deploy this config in each of your datacenters with the appropriate regional subdomain +endpoint: "https://REGION.rubiconproject.com/a/api/exchange" +endpointCompression: GZIP +geoscope: + - global disabled: true +xapi: + username: GET_FROM_MAGNITE + password: GET_FROM_MAGNITE + tracker: SAME_AS_USERNAME maintainer: email: "header-bidding@rubiconproject.com" gvlVendorID: 52 +openrtb: + version: 2.6 + gpp-supported: true capabilities: app: mediaTypes: @@ -15,7 +28,6 @@ capabilities: - video - native userSync: - # rubicon supports user syncing, but requires configuration by the host. contact this - # bidder directly at the email address in this file to ask about enabling user sync. + # rubicon supports user syncing, but requires configuration. Please contact global-support@magnite.com. supports: - redirect diff --git a/static/bidder-info/seedingAlliance.yaml b/static/bidder-info/seedingAlliance.yaml index dee9fcd6026..9dcb7701b34 100644 --- a/static/bidder-info/seedingAlliance.yaml +++ b/static/bidder-info/seedingAlliance.yaml @@ -1,4 +1,4 @@ -endpoint: "https://b.nativendo.de/cds/rtb/bid?ssp=pbs" +endpoint: "https://b.nativendo.de/cds/rtb/bid?ssp={{.AccountID}}" maintainer: email: tech@seeding-alliance.de gvlVendorID: 371 diff --git a/static/bidder-info/sharethrough.yaml b/static/bidder-info/sharethrough.yaml index 0b3479f56d2..e16129030d6 100644 --- a/static/bidder-info/sharethrough.yaml +++ b/static/bidder-info/sharethrough.yaml @@ -2,6 +2,8 @@ endpoint: "https://btlr.sharethrough.com/universal/v1?supply_id=FGMrCMMc" maintainer: email: pubgrowth.engineering@sharethrough.com gvlVendorID: 80 +openrtb: + version: 2.6 capabilities: app: mediaTypes: @@ -15,5 +17,5 @@ capabilities: - native userSync: redirect: - url: https://match.sharethrough.com/FGMrCMMc/v1?redirectUri={{.RedirectURL}} + url: https://match.sharethrough.com/FGMrCMMc/v1?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redirectUri={{.RedirectURL}} userMacro: $UID diff --git a/static/bidder-info/smaato.yaml b/static/bidder-info/smaato.yaml index 59a734c90fd..2b1b5c3fe17 100644 --- a/static/bidder-info/smaato.yaml +++ b/static/bidder-info/smaato.yaml @@ -15,6 +15,8 @@ capabilities: - video - native userSync: + # This bidder does not sync when GDPR is in-scope. Please consider removing the usersync + # block when deploying to European datacenters redirect: url: "https://s.ad.smaato.net/c/?adExInit=p&redir={{.RedirectURL}}&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}" userMacro: "$UID" diff --git a/static/bidder-info/smarthub.yaml b/static/bidder-info/smarthub.yaml index dcb65ede0ce..687c6c3fe81 100644 --- a/static/bidder-info/smarthub.yaml +++ b/static/bidder-info/smarthub.yaml @@ -1,4 +1,4 @@ -endpoint: "http://{{.Host}}-prebid.smart-hub.io/?seat={{.AccountID}}&token={{.SourceId}}" +endpoint: "http://prebid.smart-hub.io/pbserver?partnerName={{.Host}}&seat={{.AccountID}}&token={{.SourceId}}" maintainer: email: "support@smart-hub.io" capabilities: diff --git a/static/bidder-info/smartx.yaml b/static/bidder-info/smartx.yaml index 9a387ecfbd2..a3c96a57528 100644 --- a/static/bidder-info/smartx.yaml +++ b/static/bidder-info/smartx.yaml @@ -1,4 +1,6 @@ endpoint: "https://bid.smartclip.net/bid/1005" +openrtb: + version: 2.6 maintainer: email: "bidding@smartclip.tv" gvlVendorID: 115 diff --git a/static/bidder-info/smartyads.yaml b/static/bidder-info/smartyads.yaml index b36983bc7b1..21b8a0a11d6 100644 --- a/static/bidder-info/smartyads.yaml +++ b/static/bidder-info/smartyads.yaml @@ -1,6 +1,7 @@ endpoint: "http://{{.Host}}.smartyads.com/bid?rtb_seat_id={{.SourceId}}&secret_key={{.AccountID}}" maintainer: email: "support@smartyads.com" +gvlVendorID: 534 capabilities: app: mediaTypes: diff --git a/static/bidder-info/smrtconnect.yaml b/static/bidder-info/smrtconnect.yaml new file mode 100644 index 00000000000..7078248d3e9 --- /dev/null +++ b/static/bidder-info/smrtconnect.yaml @@ -0,0 +1,20 @@ +endpoint: "https://amp.smrtconnect.com/openrtb2/auction?supply_id={{.SupplyId}}" +# This bidder does not operate globally. Please consider setting "disabled: true" in European datacenters. +geoscope: + - "!EEA" +endpointCompression: gzip +maintainer: + email: "prebid@smrtconnect.com" +capabilities: + app: + mediaTypes: + - banner + - native + - video + - audio + site: + mediaTypes: + - banner + - native + - video + - audio \ No newline at end of file diff --git a/static/bidder-info/sonobi.yaml b/static/bidder-info/sonobi.yaml index 6f9afc36b3f..422ba610a6f 100644 --- a/static/bidder-info/sonobi.yaml +++ b/static/bidder-info/sonobi.yaml @@ -7,11 +7,16 @@ capabilities: mediaTypes: - banner - video + - native app: mediaTypes: - banner - video + - native userSync: + iframe: + url: "https://sync.go.sonobi.com/uc.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&loc={{.RedirectURL}}" + userMacro: "[UID]" redirect: - url: "https://sync.go.sonobi.com/us.gif?loc={{.RedirectURL}}" + url: "https://sync.go.sonobi.com/us.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&loc={{.RedirectURL}}" userMacro: "[UID]" diff --git a/static/bidder-info/sovrn.yaml b/static/bidder-info/sovrn.yaml index 62d74152b0b..fad1850d4b3 100644 --- a/static/bidder-info/sovrn.yaml +++ b/static/bidder-info/sovrn.yaml @@ -1,6 +1,7 @@ endpoint: "http://pbs.lijit.com/rtb/bid?src=prebid_server" maintainer: email: "sovrnoss@sovrn.com" +endpointCompression: gzip gvlVendorID: 13 modifyingVastXmlAllowed: true capabilities: diff --git a/static/bidder-info/sovrnXsp.yaml b/static/bidder-info/sovrnXsp.yaml new file mode 100644 index 00000000000..3cce11a551c --- /dev/null +++ b/static/bidder-info/sovrnXsp.yaml @@ -0,0 +1,12 @@ +endpoint: "http://xsp.lijit.com/json/rtb/prebid/server" +maintainer: + email: "sovrnoss@sovrn.com" +endpointCompression: gzip +gvlVendorID: 13 +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-info/streamlyn.yaml b/static/bidder-info/streamlyn.yaml new file mode 100644 index 00000000000..0cf1444ef29 --- /dev/null +++ b/static/bidder-info/streamlyn.yaml @@ -0,0 +1,2 @@ +endpoint: "http://rtba.bidsxchange.com/openrtb/{{.PublisherID}}?host={{.Host}}" +aliasOf: "limelightDigital" diff --git a/static/bidder-info/stroeerCore.yaml b/static/bidder-info/stroeerCore.yaml index 32c78590bb8..9c0904508ac 100644 --- a/static/bidder-info/stroeerCore.yaml +++ b/static/bidder-info/stroeerCore.yaml @@ -7,9 +7,11 @@ capabilities: app: mediaTypes: - banner + - video site: mediaTypes: - banner + - video userSync: # for both user syncs, stroeerCore appends the user id to end of the redirect url and does not utilize a macro iframe: diff --git a/static/bidder-info/suntContent.yaml b/static/bidder-info/suntContent.yaml index e46cc4086e0..bb5eb2ee977 100644 --- a/static/bidder-info/suntContent.yaml +++ b/static/bidder-info/suntContent.yaml @@ -1,13 +1,7 @@ -endpoint: "https://b.suntcontent.se/cds/rtb/bid?ssp=pbs" -maintainer: - email: tech@seeding-alliance.de +endpoint: "https://b.suntcontent.se/cds/rtb/bid?ssp={{.AccountID}}" +aliasOf: "seedingAlliance" gvlVendorID: 1097 -capabilities: - site: - mediaTypes: - - native - - banner userSync: redirect: url: "https://dmp.suntcontent.se/set-uuid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirect_url={{.RedirectURL}}" - userMacro: "$UID" + userMacro: "$UID" \ No newline at end of file diff --git a/static/bidder-info/synacormedia.yaml b/static/bidder-info/synacormedia.yaml index 2a796ae839f..ccbba0700a4 100644 --- a/static/bidder-info/synacormedia.yaml +++ b/static/bidder-info/synacormedia.yaml @@ -1,15 +1,3 @@ # DEPRECATED: Use imds bidder instead +aliasOf: imds endpoint: "https://pbs.technoratimedia.com/openrtb/bids/{{.AccountID}}?src={{.SourceId}}&adapter=synacormedia" -maintainer: - email: "eng-demand@imds.tv" -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video -userSync: - key: "imds" diff --git a/static/bidder-info/taboola.yaml b/static/bidder-info/taboola.yaml index 436f746959a..0fccee145bc 100644 --- a/static/bidder-info/taboola.yaml +++ b/static/bidder-info/taboola.yaml @@ -13,8 +13,8 @@ capabilities: - native userSync: redirect: - url: https://trc.taboola.com/sg/ps/1/cm?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}} + url: "https://trc.taboola.com/sg/ps/1/cm?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redirect={{.RedirectURL}}" userMacro: "" iframe: - url: https://cdn.taboola.com/scripts/ps-sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}} + url: "https://cdn.taboola.com/scripts/ps-sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redirect={{.RedirectURL}}" userMacro: "" diff --git a/static/bidder-info/teads.yaml b/static/bidder-info/teads.yaml index 5dc428ddf7b..307b631efb4 100644 --- a/static/bidder-info/teads.yaml +++ b/static/bidder-info/teads.yaml @@ -1,6 +1,6 @@ -endpoint: "https://a.teads.tv/prebid-server/bid-request" +endpoint: "https://psrv.teads.tv/prebid-server/bid-request" maintainer: - email: "support-sdk@teads.com" + email: "innov-ssp@teads.tv" gvlVendorID: 132 capabilities: app: diff --git a/static/bidder-info/tgm.yaml b/static/bidder-info/tgm.yaml new file mode 100644 index 00000000000..29d2039ee3f --- /dev/null +++ b/static/bidder-info/tgm.yaml @@ -0,0 +1 @@ +aliasOf: "limelightDigital" diff --git a/static/bidder-info/theadx.yaml b/static/bidder-info/theadx.yaml new file mode 100644 index 00000000000..01d3312a561 --- /dev/null +++ b/static/bidder-info/theadx.yaml @@ -0,0 +1,23 @@ +endpoint: "https://ssp.theadx.com/request?pbs=1" +maintainer: + email: "ssp@theadx.com" +gvlVendorID: 556 +debug: + allow: true +capabilities: + app: + mediaTypes: + - banner + - native + - video + - audio + site: + mediaTypes: + - banner + - native + - video + - audio +userSync: + redirect: + url: "https://ssp.theadx.com/cookie?redirect_url={{.RedirectURL}}&?pbs=1" + userMacro: "$UID" diff --git a/static/bidder-info/thetradedesk.yaml b/static/bidder-info/thetradedesk.yaml new file mode 100644 index 00000000000..e1354c86ddc --- /dev/null +++ b/static/bidder-info/thetradedesk.yaml @@ -0,0 +1,23 @@ +endpoint: "https://direct.adsrvr.org/bid/bidder/{{.SupplyId}}" +maintainer: + email: "Prebid-Maintainers@thetradedesk.com" +gvlVendorID: 21 +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native +userSync: + # TheTradeDesk supports user syncing, but requires configuration by the host. Contact a technical account manager + # or this bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect + - iframe +openrtb: + gpp-supported: true diff --git a/static/bidder-info/tredio.yaml b/static/bidder-info/tredio.yaml new file mode 100644 index 00000000000..5ed85a937df --- /dev/null +++ b/static/bidder-info/tredio.yaml @@ -0,0 +1,5 @@ +endpoint: "http://tredio-prebid.smart-hub.io/pbserver/?seat={{.AccountID}}&token={{.SourceId}}" +aliasOf: "smarthub" +userSync: + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-info/triplelift.yaml b/static/bidder-info/triplelift.yaml index 605bcc71e6e..79a8951680f 100644 --- a/static/bidder-info/triplelift.yaml +++ b/static/bidder-info/triplelift.yaml @@ -12,9 +12,10 @@ capabilities: - banner - video userSync: - # Triplelift supports user syncing but requires configuration by the host as the RedirectURL domain must be allowlisted. - # Contact this bidder directly at the email address above to ask about enabling user sync. - # + # Triplelift supports user syncing but requires configuration by the host as the RedirectURL domain must be allowlisted. + # If you are a publisher hosting your own Prebid Server, contact this bidder directly at the email address above to ask about enabling user sync. + # If you are a Prebid Server Host, please have your publisher contact the bidder. + # iframe: url: "https://eb2.3lift.com/sync?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}" userMacro: $UID @@ -23,4 +24,5 @@ userSync: userMacro: "$UID" endpointCompression: "GZIP" openrtb: + version: 2.6 gpp-supported: true \ No newline at end of file diff --git a/static/bidder-info/triplelift_native.yaml b/static/bidder-info/triplelift_native.yaml index ff93b544c4c..c5e08152692 100644 --- a/static/bidder-info/triplelift_native.yaml +++ b/static/bidder-info/triplelift_native.yaml @@ -12,8 +12,9 @@ capabilities: mediaTypes: - native userSync: - # Triplelift supports user syncing but requires configuration by the host as the RedirectURL domain must be allowlisted. - # Contact this bidder directly at the email address above to ask about enabling user sync. + # Triplelift supports user syncing but requires configuration by the host as the RedirectURL domain must be allowlisted. + # If you are a publisher hosting your own Prebid Server, contact this bidder directly at the email address above to ask about enabling user sync. + # If you are a Prebid Server Host, please have your publisher contact the bidder. # iframe: url: "https://eb2.3lift.com/sync?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}" @@ -22,4 +23,5 @@ userSync: url: "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}" userMacro: "$UID" openrtb: + version: 2.6 gpp-supported: true \ No newline at end of file diff --git a/static/bidder-info/trustedstack.yaml b/static/bidder-info/trustedstack.yaml new file mode 100644 index 00000000000..ffa48d261d4 --- /dev/null +++ b/static/bidder-info/trustedstack.yaml @@ -0,0 +1,24 @@ +endpoint: "https://prebid-adapter.trustedstack.com/rtb/pb/trustedstacks2s" +extra_info: "https://trustedstack.golang.pbs.com" +maintainer: + email: "product@trustedstack.com" +gvlVendorID: 1288 +endpointCompression: gzip +openrtb: + version: 2.6 +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native +userSync: + redirect: + url: https://hb.trustedstack.com/cksync?cs=1&type=pbs&ovsid=setstatuscode&bidder=trustedstack&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}} + userMacro: "" \ No newline at end of file diff --git a/static/bidder-info/trustx.yaml b/static/bidder-info/trustx.yaml index 5e407782e6a..1c60ac34c2f 100644 --- a/static/bidder-info/trustx.yaml +++ b/static/bidder-info/trustx.yaml @@ -1,17 +1,9 @@ -endpoint: "https://grid.bidswitch.net/sp_bid?sp=trustx" +aliasOf: grid maintainer: email: "grid-tech@themediagrid.com" gvlVendorID: 686 -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video userSync: redirect: url: "https://x.bidswitch.net/check_uuid/{{.RedirectURL}}?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}" - userMacro: "${BSW_UUID}" \ No newline at end of file + userMacro: "${BSW_UUID}" + \ No newline at end of file diff --git a/static/bidder-info/unruly.yaml b/static/bidder-info/unruly.yaml index 6389f831a64..1f00d816d73 100644 --- a/static/bidder-info/unruly.yaml +++ b/static/bidder-info/unruly.yaml @@ -1,7 +1,12 @@ endpoint: "https://targeting.unrulymedia.com/unruly_prebid_server" +geoscope: + - global maintainer: email: "prebidsupport@unrulygroup.com" gvlVendorID: 36 +openrtb: + version: 2.6 + gpp-supported: true capabilities: app: mediaTypes: @@ -14,4 +19,4 @@ capabilities: userSync: redirect: url: "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" - userMacro: "[RX_UUID]" \ No newline at end of file + userMacro: "[RX_UUID]" diff --git a/static/bidder-info/vidazoo.yaml b/static/bidder-info/vidazoo.yaml new file mode 100644 index 00000000000..a58f6849501 --- /dev/null +++ b/static/bidder-info/vidazoo.yaml @@ -0,0 +1,20 @@ +endpoint: "https://prebidsrvr.cootlogix.com/openrtb/" +maintainer: + email: "dev@vidazoo.com" +gvlVendorID: 744 +endpointCompression: gzip +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video +userSync: + iframe: + url: https://sync.cootlogix.com/api/user/html/pbs_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}} + userMacro: ${userId} +openrtb: + gpp_supported: true \ No newline at end of file diff --git a/static/bidder-info/vimayx.yaml b/static/bidder-info/vimayx.yaml new file mode 100644 index 00000000000..2104b17df10 --- /dev/null +++ b/static/bidder-info/vimayx.yaml @@ -0,0 +1,2 @@ +endpoint: "http://vimayx-prebid.smart-hub.io/pbserver/?seat={{.AccountID}}&token={{.SourceId}}" +aliasOf: "smarthub" diff --git a/static/bidder-info/liftoff.yaml b/static/bidder-info/vungle.yaml similarity index 72% rename from static/bidder-info/liftoff.yaml rename to static/bidder-info/vungle.yaml index 577439dbb97..43470bc7020 100644 --- a/static/bidder-info/liftoff.yaml +++ b/static/bidder-info/vungle.yaml @@ -1,9 +1,12 @@ endpoint: "https://rtb.ads.vungle.com/bid/t/c770f32" maintainer: - email: platform-ssp@liftoff.io + email: vxssp@liftoff.io endpointCompression: gzip modifyingVastXmlAllowed: true capabilities: app: mediaTypes: - video + site: + mediaTypes: + - video diff --git a/static/bidder-info/yahooAds.yaml b/static/bidder-info/yahooAds.yaml index a2581387152..c53d1b14e0c 100644 --- a/static/bidder-info/yahooAds.yaml +++ b/static/bidder-info/yahooAds.yaml @@ -1,6 +1,6 @@ endpoint: "https://s2shb.ssp.yahoo.com/admax/bid/partners/PBS" maintainer: - email: "hb-fe-tech@yahooinc.com" + email: "prebid-tech-team@yahooinc.com" gvlVendorID: 25 capabilities: app: diff --git a/static/bidder-info/yahooAdvertising.yaml b/static/bidder-info/yahooAdvertising.yaml index 3798adf7ca2..f9f34fbfbd0 100644 --- a/static/bidder-info/yahooAdvertising.yaml +++ b/static/bidder-info/yahooAdvertising.yaml @@ -1,16 +1,4 @@ -endpoint: "https://s2shb.ssp.yahoo.com/admax/bid/partners/PBS" -maintainer: - email: "hb-fe-tech@yahooinc.com" -gvlVendorID: 25 -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video +aliasOf: yahooAds userSync: # yahooAdvertising supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. diff --git a/static/bidder-info/yahoossp.yaml b/static/bidder-info/yahoossp.yaml index 8c23c01e181..81695624dfa 100644 --- a/static/bidder-info/yahoossp.yaml +++ b/static/bidder-info/yahoossp.yaml @@ -1,16 +1,6 @@ -endpoint: "https://s2shb.ssp.yahoo.com/admax/bid/partners/PBS" +aliasOf: yahooAds maintainer: email: "hb-fe-tech@oath.com" -gvlVendorID: 25 -capabilities: - app: - mediaTypes: - - banner - - video - site: - mediaTypes: - - banner - - video userSync: # yahoossp supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. diff --git a/static/bidder-info/yandex.yaml b/static/bidder-info/yandex.yaml new file mode 100644 index 00000000000..8c5be2f70d4 --- /dev/null +++ b/static/bidder-info/yandex.yaml @@ -0,0 +1,13 @@ +endpoint: "https://bs-metadsp.yandex.ru/prebid/{{.PageID}}?ssp-id=10500" +endpointCompression: gzip +maintainer: + email: "prebid@yandex-team.ru" +capabilities: + site: + mediaTypes: + - banner + - native +userSync: + redirect: + url: https://yandex.ru/an/mapuid/yandex/?ssp-id=10500&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&location={{.RedirectURL}} + userMacro: "{YANDEXUID}" diff --git a/static/bidder-info/yieldmo.yaml b/static/bidder-info/yieldmo.yaml index 9356a541f60..17b25ecbbf1 100644 --- a/static/bidder-info/yieldmo.yaml +++ b/static/bidder-info/yieldmo.yaml @@ -2,6 +2,8 @@ endpoint: "https://ads.yieldmo.com/exchange/prebid-server" maintainer: email: "prebid@yieldmo.com" gvlVendorID: 173 +openrtb: + version: 2.6 capabilities: app: mediaTypes: @@ -13,5 +15,5 @@ capabilities: - video userSync: redirect: - url: "https://ads.yieldmo.com/pbsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri={{.RedirectURL}}" + url: "https://ads.yieldmo.com/pbsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redirectUri={{.RedirectURL}}" userMacro: "$UID" \ No newline at end of file diff --git a/static/bidder-info/zeta_global_ssp.yaml b/static/bidder-info/zeta_global_ssp.yaml index 3cc2ecb5297..479f024c1b7 100644 --- a/static/bidder-info/zeta_global_ssp.yaml +++ b/static/bidder-info/zeta_global_ssp.yaml @@ -1,4 +1,4 @@ -endpoint: https://ssp.disqus.com/bid/prebid-server?sid=GET_SID_FROM_ZETA&shortname=GET_SHORTNAME_FROM_ZETA +endpoint: https://ssp.disqus.com/bid/prebid-server?sid=GET_SID_FROM_ZETA endpointCompression: gzip maintainer: email: DL-Zeta-SSP@zetaglobal.com @@ -15,6 +15,6 @@ capabilities: - video userSync: redirect: - url: https://ssp.disqus.com/redirectuser?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}&partner=GET_SHORTNAME_FROM_ZETA + url: https://ssp.disqus.com/redirectuser?sid=GET_SID_FROM_ZETA&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}} userMacro: 'BUYERUID' diff --git a/static/bidder-info/zmaticoo.yaml b/static/bidder-info/zmaticoo.yaml new file mode 100644 index 00000000000..ae7d3eb0100 --- /dev/null +++ b/static/bidder-info/zmaticoo.yaml @@ -0,0 +1,9 @@ +endpoint: "https://bid.zmaticoo.com/prebid/bid" +maintainer: + email: "adam.li@eclicktech.com.cn" +capabilities: + app: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-params/adelement.json b/static/bidder-params/adelement.json new file mode 100644 index 00000000000..d293fb6d7fd --- /dev/null +++ b/static/bidder-params/adelement.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Adelement Params", + "description": "A schema which validates params accepted by the Adelement", + "type": "object", + "properties": { + "supply_id": { + "type": "string", + "description": "Supply id", + "minLength": 1 + } + }, + "required": ["supply_id"] +} diff --git a/static/bidder-params/admatic.json b/static/bidder-params/admatic.json new file mode 100644 index 00000000000..016a95cfb0a --- /dev/null +++ b/static/bidder-params/admatic.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "AdMatic Adapter Params", + "description": "A schema which validates params accepted by the AdMatic adapter", + "type": "object", + "properties": { + "host": { + "type": "string", + "description": "Host Name" + }, + "networkId": { + "type": "integer", + "description": "AdMatic Network Id" + } + }, + "required": [ + "host", + "networkId" + ] +} \ No newline at end of file diff --git a/static/bidder-params/adtarget.json b/static/bidder-params/adtarget.json index 195bf2dd430..980fb623786 100644 --- a/static/bidder-params/adtarget.json +++ b/static/bidder-params/adtarget.json @@ -14,7 +14,10 @@ "description": "An ID which identifies the site selling the impression" }, "aid": { - "type": "integer", + "type": [ + "integer", + "string" + ], "description": "An ID which identifies the channel" }, "bidFloor": { diff --git a/static/bidder-params/adtelligent.json b/static/bidder-params/adtelligent.json index db7931e1ec0..9b241bee149 100644 --- a/static/bidder-params/adtelligent.json +++ b/static/bidder-params/adtelligent.json @@ -14,7 +14,10 @@ "description": "An ID which identifies the site selling the impression" }, "aid": { - "type": "integer", + "type": [ + "integer", + "string" + ], "description": "An ID which identifies the channel" }, "bidFloor": { diff --git a/static/bidder-params/adtonos.json b/static/bidder-params/adtonos.json new file mode 100644 index 00000000000..e43d23e2b4b --- /dev/null +++ b/static/bidder-params/adtonos.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "AdTonos Adapter Params", + "description": "A schema which validates params accepted by the AdTonos adapter", + + "type": "object", + "properties": { + "supplierId": { + "type": "string", + "description": "ID of the supplier account in AdTonos platform" + } + }, + "required": ["supplierId"] +} \ No newline at end of file diff --git a/static/bidder-params/alkimi.json b/static/bidder-params/alkimi.json new file mode 100644 index 00000000000..93e09022469 --- /dev/null +++ b/static/bidder-params/alkimi.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Alkimi Adapter Params", + "description": "A schema which validates params accepted by the Alkimi adapter", + "type": "object", + + "properties": { + "token": { + "type": "string", + "description": "Publisher Token provided by Alkimi" + }, + "bidFloor": { + "type": "number", + "description": "The minimum CPM price in USD", + "minimum": 0 + }, + "instl": { + "type": "number", + "description": "Set to 1 if using interstitial (default: 0)" + }, + "exp": { + "type": "number", + "description": "Advisory as to the number of seconds that may elapse between the auction and the actual impression" + } + }, + + "required": ["token"] +} diff --git a/static/bidder-params/aso.json b/static/bidder-params/aso.json new file mode 100644 index 00000000000..edb3c0feb9c --- /dev/null +++ b/static/bidder-params/aso.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Adserver.Online Adapter Params", + "description": "A schema which validates params accepted by the aso adapter", + "type": "object", + "properties": { + "zone": { + "type": "integer", + "description": "An ID which identifies the zone selling the impression" + } + }, + "required": ["zone"] +} diff --git a/static/bidder-params/bidmatic.json b/static/bidder-params/bidmatic.json new file mode 100644 index 00000000000..b3002a55ac5 --- /dev/null +++ b/static/bidder-params/bidmatic.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Bidmatic Adapter Params", + "description": "A schema which validates params accepted by the Bidmatic adapter", + + "type": "object", + "properties": { + "placementId": { + "type": "integer", + "description": "An ID which identifies this placement of the impression" + }, + "siteId": { + "type": "integer", + "description": "An ID which identifies the site selling the impression" + }, + "source": { + "type": [ + "integer", + "string" + ], + "description": "An ID which identifies the channel" + }, + "bidFloor": { + "type": "number", + "description": "BidFloor, US Dollars" + } + }, + "required": ["source"] +} diff --git a/static/bidder-params/bigoad.json b/static/bidder-params/bigoad.json new file mode 100644 index 00000000000..418d2186969 --- /dev/null +++ b/static/bidder-params/bigoad.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "BigoAd Adapter Params", + "description": "A schema which validates params accepted by the BigoAd adapter", + "type": "object", + "properties": { + "sspid": { + "type": "string", + "description": "Special id provided by BigoAd" + } + }, + "required": ["sspid"] + } \ No newline at end of file diff --git a/static/bidder-params/bizzclick.json b/static/bidder-params/blasto.json similarity index 66% rename from static/bidder-params/bizzclick.json rename to static/bidder-params/blasto.json index 429e2948f8a..a6490ab9871 100644 --- a/static/bidder-params/bizzclick.json +++ b/static/bidder-params/blasto.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Bizzclick Adapter Params", - "description": "A schema which validates params accepted by the Bizzclick adapter", + "title": "Blasto Adapter Params", + "description": "A schema which validates params accepted by the Blasto adapter", "type": "object", "properties": { "accountId": { @@ -9,11 +9,14 @@ "description": "Account id", "minLength": 1 }, - "placementId": { + "sourceId": { "type": "string", - "description": "PlacementId id", + "description": "Source id", "minLength": 1 } }, - "required": ["accountId", "placementId"] + "required": [ + "accountId", + "sourceId" + ] } \ No newline at end of file diff --git a/static/bidder-params/bwx.json b/static/bidder-params/bwx.json new file mode 100644 index 00000000000..873aadad40c --- /dev/null +++ b/static/bidder-params/bwx.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "BoldwinX Adapter Params", + "description": "A schema which validates params accepted by the BoldwinX adapter", + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "BoldwinX environment", + "minLength": 1 + }, + "pid": { + "type": "string", + "description": "Unique placement ID", + "minLength": 1 + } + }, + "required": [ + "pid" + ] + } diff --git a/static/bidder-params/cointraffic.json b/static/bidder-params/cointraffic.json new file mode 100644 index 00000000000..8a749fdfac3 --- /dev/null +++ b/static/bidder-params/cointraffic.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Cointraffic Adapter Params", + "description": "A schema which validates params accepted by the Cointraffic adapter", + "type": "object", + "properties": { + "placementId": { + "type": "string", + "minLength": 1, + "description": "Ad placement identifier" + } + }, + "required": [ + "placementId" + ] +} diff --git a/static/bidder-params/concert.json b/static/bidder-params/concert.json new file mode 100644 index 00000000000..5dd82b4d486 --- /dev/null +++ b/static/bidder-params/concert.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Concert Adapter Params", + "description": "A schema which validates params accepted by the Concert adapter", + "type": "object", + "properties": { + "partnerId": { + "type": "string", + "description": "The partner id assigned by concert.", + "minLength": 1 + }, + "placementId": { + "type": "integer", + "description": "The placement id." + }, + "site": { + "type": "string", + "description": "The site name." + }, + "slot": { + "type": "string", + "description": "The slot name." + }, + "sizes": { + "type": "array", + "description": "All sizes this ad unit accepts.", + "items": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 2, + "maxItems": 2 + } + } + }, + "required": ["partnerId"] +} \ No newline at end of file diff --git a/static/bidder-params/connectad.json b/static/bidder-params/connectad.json index 15f4ab66bf3..224345b96fc 100644 --- a/static/bidder-params/connectad.json +++ b/static/bidder-params/connectad.json @@ -1,24 +1,30 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "ConnectAd S2S dapter Params", - "description": "A schema which validates params accepted by the ConnectAd Adapter", - - "type": "object", - "properties": { - "networkId": { - "type": "integer", - "description": "NetworkId" - }, - "siteId": { - "type": "integer", - "description": "SiteId" - }, - "bidfloor": { - "type": "number", - "description": "Requests Floorprice" - } + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "ConnectAd S2S dapter Params", + "description": "A schema which validates params accepted by the ConnectAd Adapter", + + "type": "object", + "properties": { + "networkId": { + "type": [ + "integer", + "string" + ], + "description": "NetworkId" + }, + "siteId": { + "type": [ + "integer", + "string" + ], + "description": "SiteId" }, - "required": ["networkId", "siteId"] - } + "bidfloor": { + "type": "number", + "description": "Requests Floorprice" + } + }, + "required": ["networkId", "siteId"] +} diff --git a/static/bidder-params/consumable.json b/static/bidder-params/consumable.json index b1db53568b9..7709ab09d37 100644 --- a/static/bidder-params/consumable.json +++ b/static/bidder-params/consumable.json @@ -24,7 +24,15 @@ "type": "string", "description": "The unit name from Consumable (expected to be a valid CSS class name)", "pattern": "^-?[_a-zA-Z]+[_a-zA-Z0-9-]*$" + }, + "placementId": { + "type": "string", + "description": "The placementID from Consumable, Required for non-site requests", + "pattern": "^[a-zA-Z0-9]+$" } }, - "required": ["siteId", "networkId","unitId"] + "oneOf": [ + {"required": ["siteId", "networkId","unitId"] }, + {"required": ["placementId"] } + ] } diff --git a/static/bidder-params/copper6ssp.json b/static/bidder-params/copper6ssp.json new file mode 100644 index 00000000000..9467e48e9b9 --- /dev/null +++ b/static/bidder-params/copper6ssp.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Copper6SSPs Adapter Params", + "description": "A schema which validates params accepted by the Copper6SSP adapter", + "type": "object", + "properties": { + "placementId": { + "type": "string", + "minLength": 1, + "description": "Placement ID" + }, + "endpointId": { + "type": "string", + "minLength": 1, + "description": "Endpoint ID" + } + }, + "oneOf": [ + { "required": ["placementId"] }, + { "required": ["endpointId"] } + ] +} \ No newline at end of file diff --git a/static/bidder-params/displayio.json b/static/bidder-params/displayio.json new file mode 100644 index 00000000000..1a3fe3875d4 --- /dev/null +++ b/static/bidder-params/displayio.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Display.io Adapter Params", + "description": "A schema which validates params accepted by the Display.io adapter", + "type": "object", + "properties": { + "publisherId": { + "type": "string", + "description": "Publisher Id" + }, + "inventoryId": { + "type": "string", + "description": "Inventory Id" + }, + "placementId": { + "type": "string", + "description": "Placement Id" + } + }, + "required": [ + "publisherId", + "inventoryId", + "placementId" + ] +} \ No newline at end of file diff --git a/static/bidder-params/driftpixel.json b/static/bidder-params/driftpixel.json new file mode 100644 index 00000000000..8cb2e6d6fef --- /dev/null +++ b/static/bidder-params/driftpixel.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "DriftPixel Adapter Params", + "description": "A schema which validates params accepted by the DriftPixel adapter", + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "DriftPixel environment", + "minLength": 1 + }, + "pid": { + "type": "string", + "description": "Unique placement ID", + "minLength": 1 + } + }, + "required": [ + "pid" + ] + } diff --git a/static/bidder-params/epsilon.json b/static/bidder-params/epsilon.json deleted file mode 100644 index a33a68325e4..00000000000 --- a/static/bidder-params/epsilon.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Epsilon Adapter Params", - "description": "A schema which validates params accepted by the Epsilon adapter.", - "type": "object", - "properties": { - "site_id": { - "type": "string", - "description": "An Epsilon specific ID which identifies the site." - }, - "secure": { - "type": "integer", - "description": "Override http/https context on ad markup." - }, - "bidfloor" : { - "type": "number", - "description": "Minimum bid price that will be considered." - }, - "tag_id": { - "type": "string", - "description": "Identifies specific ad placement." - }, - "position": { - "type": "integer", - "description": "Ad position on screen." - }, - "mimes": { - "type": "array", - "description": "Array of content MIME types. For videos only.", - "items": { - "type": "string" - } - }, - "maxduration": { - "type": "integer", - "description": "Maximum duration in seconds. For videos only." - }, - "api": { - "type": "array", - "description": "Array of supported API frameworks. For videos only.", - "items": { - "type": "integer" - } - }, - "protocols": { - "type": "array", - "description": "Array of supported video protocols. For videos only.", - "items": { - "type": "integer" - } - } - }, - "required": ["site_id"] -} \ No newline at end of file diff --git a/static/bidder-params/escalax.json b/static/bidder-params/escalax.json new file mode 100644 index 00000000000..3045e7f1898 --- /dev/null +++ b/static/bidder-params/escalax.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Escalax Adapter Params", + "description": "A schema which validates params accepted by the Escalax adapter", + "type": "object", + "properties": { + "accountId": { + "type": "string", + "description": "Account id", + "minLength": 1 + }, + "sourceId": { + "type": "string", + "description": "Source id", + "minLength": 1 + } + }, + "required": [ + "accountId", + "sourceId" + ] +} \ No newline at end of file diff --git a/static/bidder-params/freewheel-ssp.json b/static/bidder-params/freewheel-ssp.json deleted file mode 100644 index 23375f7603c..00000000000 --- a/static/bidder-params/freewheel-ssp.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "FreewheelSSP old Adapter Params", - "description": "A schema which validates params accepted by the FreewheelSSP adapter", - "type": "object", - - "properties": { - "zoneId": { - "type": ["integer", "string"], - "description": "Zone ID" - } - }, - - "required": ["zoneId"] -} diff --git a/static/bidder-params/gumgum.json b/static/bidder-params/gumgum.json index 95f05e7d517..c9972713f87 100644 --- a/static/bidder-params/gumgum.json +++ b/static/bidder-params/gumgum.json @@ -20,6 +20,10 @@ "slot": { "type": "integer", "description": "A slot id used to identify a slot placement mapped to a GumGum zone or publisher" + }, + "product": { + "type": "string", + "description": "Product param that allow support for Desktop Skins - display and video" } }, "anyOf": [ @@ -34,4 +38,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/static/bidder-params/improvedigital.json b/static/bidder-params/improvedigital.json index 55412c2f513..7c44faf3bed 100644 --- a/static/bidder-params/improvedigital.json +++ b/static/bidder-params/improvedigital.json @@ -7,16 +7,12 @@ "placementId": { "type": "integer", "minimum": 1, - "description": "An ID which identifies this placement of the impression" + "description": "The placement ID from Improve Digital" }, "publisherId": { "type": "integer", "minimum": 1, - "description": "An ID which identifies publisher. Required when using a placementKey" - }, - "placementKey": { - "type": "string", - "description": "An uniq name which identifies this placement of the impression. Must be used with publisherId" + "description": "The publisher ID from Improve Digital" }, "keyValues": { "type": "object", @@ -32,13 +28,12 @@ "type": "integer" } }, - "required": ["w", "h"], + "required": [ + "w", + "h" + ], "description": "Placement size" } }, - "oneOf": [{ - "required": ["placementId"] - }, { - "required": ["publisherId", "placementKey"] - }] -} + "required": ["placementId"] +} \ No newline at end of file diff --git a/static/bidder-params/loyal.json b/static/bidder-params/loyal.json new file mode 100644 index 00000000000..9e367029d35 --- /dev/null +++ b/static/bidder-params/loyal.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Loyal Adapter Params", + "description": "A schema which validates params accepted by the Loyal adapter", + "type": "object", + + "properties": { + "placementId": { + "type": "string", + "minLength": 1, + "description": "Placement ID" + }, + "endpointId": { + "type": "string", + "minLength": 1, + "description": "Endpoint ID" + } + }, + "oneOf": [ + { "required": ["placementId"] }, + { "required": ["endpointId"] } + ] + } \ No newline at end of file diff --git a/static/bidder-params/mediago.json b/static/bidder-params/mediago.json new file mode 100644 index 00000000000..ce482964d85 --- /dev/null +++ b/static/bidder-params/mediago.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "MediaGo Adapter Params", + "description": "A schema which validates params accepted by the MediaGo adapter", + "type": "object", + "properties": { + "token": { + "type": "string", + "description": "Publisher token,communicate with MediaGo to obtain it. This parameter expects all imps to be the same.", + "minLength": 1 + }, + "region": { + "type": "string", + "enum": ["US", "EU", "APAC"], + "description": "Server region for PBS request: US for US Region, EU for EU Region, APAC for APAC Region, default is US. This parameter expects all imps to be the same" + }, + "placementId": { + "type": "string", + "description": "The AD placement ID.", + "minLength": 1 + } + }, + "required": ["token"] +} \ No newline at end of file diff --git a/static/bidder-params/melozen.json b/static/bidder-params/melozen.json new file mode 100644 index 00000000000..6b5cef5b3fd --- /dev/null +++ b/static/bidder-params/melozen.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "MeloZen Adapter Params", + "description": "A schema which validates params accepted by the MeloZen adapter", + "type": "object", + "properties": { + "pubId": { + "type": "string", + "minLength": 1, + "description": "The unique identifier for the publisher." + } + }, + "required": ["pubId"] +} diff --git a/static/bidder-params/metax.json b/static/bidder-params/metax.json new file mode 100644 index 00000000000..3c336c2be3e --- /dev/null +++ b/static/bidder-params/metax.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "MetaX Adapter Params", + "description": "A schema which validates params accepted by the MetaX adapter", + "type": "object", + "properties": { + "publisherId": { + "type": "integer", + "description": "An ID which identifies the publisher", + "minimum": 1 + }, + "adunit": { + "type": "integer", + "description": "An ID which identifies the adunit", + "minimum": 1 + } + }, + "required": ["publisherId", "adunit"] +} diff --git a/static/bidder-params/minutemedia.json b/static/bidder-params/minutemedia.json new file mode 100644 index 00000000000..c4c475b132f --- /dev/null +++ b/static/bidder-params/minutemedia.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "MinuteMedia Adapter Params", + "description": "A schema which validates params accepted by the MinuteMedia adapter", + "type": "object", + "properties": { + "org": { + "type": "string", + "description": "The organization ID.", + "minLength": 1 + } + }, + "required": ["org"] +} \ No newline at end of file diff --git a/static/bidder-params/missena.json b/static/bidder-params/missena.json new file mode 100644 index 00000000000..c9e20e5a828 --- /dev/null +++ b/static/bidder-params/missena.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Missena Adapter Params", + "description": "A schema which validates params accepted by the Missena adapter", + "type": "object", + "properties": { + "apiKey": { + "type": "string", + "description": "API Key", + "minLength": 1 + }, + "placement": { + "type": "string", + "description": "Placement Type (Sticky, Header, ...)" + }, + "test": { + "type": "string", + "description": "Test Mode" + } + }, + "required": [ + "apiKey" + ] +} \ No newline at end of file diff --git a/static/bidder-params/nativo.json b/static/bidder-params/nativo.json new file mode 100644 index 00000000000..0a8139a0838 --- /dev/null +++ b/static/bidder-params/nativo.json @@ -0,0 +1,7 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Nativo Adapter Params", + "description": "A schema which validates params accepted by the Nativo adapter", + "type": "object", + "properties": {} +} \ No newline at end of file diff --git a/static/bidder-params/oms.json b/static/bidder-params/oms.json new file mode 100644 index 00000000000..f33286d10d9 --- /dev/null +++ b/static/bidder-params/oms.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Online Media Solutions Adapter Params", + "description": "A schema which validates params accepted by the OMS adapter", + "type": "object", + "properties": { + "pid": { + "type": "string", + "description": "An id used to identify OMS publisher.", + "minLength": 5 + } + }, + "required": ["pid"] +} diff --git a/static/bidder-params/openweb.json b/static/bidder-params/openweb.json index ec5766ad663..62a4853ed65 100644 --- a/static/bidder-params/openweb.json +++ b/static/bidder-params/openweb.json @@ -2,25 +2,34 @@ "$schema": "http://json-schema.org/draft-04/schema#", "title": "OpenWeb Adapter Params", "description": "A schema which validates params accepted by the OpenWeb adapter", - "type": "object", "properties": { "placementId": { - "type": "integer", - "description": "An ID which identifies this placement of the impression" - }, - "siteId": { - "type": "integer", - "description": "An ID which identifies the site selling the impression" + "type": "string", + "description": "An ID which identifies this placement of the impression", + "minLength": 1 }, "aid": { "type": "integer", - "description": "An ID which identifies the channel" + "description": "Deprecated: An ID which identifies the channel" }, - "bidFloor": { - "type": "number", - "description": "BidFloor, US Dollars" + "org": { + "type": "string", + "description": "The organization ID.", + "minLength": 1 } }, - "required": ["aid"] + "required": [ "placementId" ], + "oneOf": [ + { + "required": [ + "aid" + ] + }, + { + "required": [ + "org" + ] + } + ] } diff --git a/static/bidder-params/openx.json b/static/bidder-params/openx.json index 6dbd10178e4..0b58e03a15c 100644 --- a/static/bidder-params/openx.json +++ b/static/bidder-params/openx.json @@ -6,8 +6,9 @@ "type": "object", "properties": { "unit": { - "type": "string", + "type": ["number", "string"], "description": "The ad unit id.", + "minimum": 0, "pattern": "^[0-9]+$" }, "delDomain": { @@ -22,9 +23,10 @@ "format": "uuid" }, "customFloor": { - "type": "number", + "type": ["number", "string"], "description": "The minimum CPM price in USD.", - "minimum": 0 + "minimum": 0, + "pattern": "^[0-9]+(\\.[0-9]+)?$" }, "customParams": { "type": "object", diff --git a/static/bidder-params/oraki.json b/static/bidder-params/oraki.json new file mode 100644 index 00000000000..610c1f0e8c7 --- /dev/null +++ b/static/bidder-params/oraki.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Oraki Adapter Params", + "description": "A schema which validates params accepted by the Oraki adapter", + "type": "object", + "properties": { + "placementId": { + "type": "string", + "minLength": 1, + "description": "Placement ID" + }, + "endpointId": { + "type": "string", + "minLength": 1, + "description": "Endpoint ID" + } + }, + "oneOf": [ + { "required": ["placementId"] }, + { "required": ["endpointId"] } + ] +} diff --git a/static/bidder-params/ownadx.json b/static/bidder-params/ownadx.json index f529e74cb01..e0e09a7e9f7 100644 --- a/static/bidder-params/ownadx.json +++ b/static/bidder-params/ownadx.json @@ -18,10 +18,5 @@ "description": "Token ID" } }, - - "oneOf": [ - { "required": ["sspId"] }, - { "required": ["feedId"] }, - { "required": ["token"] } - ] + "required": ["sspId","seatId","tokenId"] } diff --git a/static/bidder-params/playdigo.json b/static/bidder-params/playdigo.json new file mode 100644 index 00000000000..301ffdf1ca6 --- /dev/null +++ b/static/bidder-params/playdigo.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Playdigo Adapter Params", + "description": "A schema which validates params accepted by the Playdigo adapter", + "type": "object", + + "properties": { + "placementId": { + "type": "string", + "minLength": 1, + "description": "Placement ID" + }, + "endpointId": { + "type": "string", + "minLength": 1, + "description": "Endpoint ID" + } + }, + "oneOf": [ + { "required": ["placementId"] }, + { "required": ["endpointId"] } + ] +} \ No newline at end of file diff --git a/static/bidder-params/pubrise.json b/static/bidder-params/pubrise.json new file mode 100644 index 00000000000..0d972da45e9 --- /dev/null +++ b/static/bidder-params/pubrise.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Pubrise Adapter Params", + "description": "A schema which validates params accepted by the Pubrise adapter", + "type": "object", + "properties": { + "placementId": { + "type": "string", + "minLength": 1, + "description": "Placement ID" + }, + "endpointId": { + "type": "string", + "minLength": 1, + "description": "Endpoint ID" + } + }, + "oneOf": [ + { "required": ["placementId"] }, + { "required": ["endpointId"] } + ] +} \ No newline at end of file diff --git a/static/bidder-params/pulsepoint.json b/static/bidder-params/pulsepoint.json index 673fd2efd6f..05d1e159a43 100644 --- a/static/bidder-params/pulsepoint.json +++ b/static/bidder-params/pulsepoint.json @@ -5,11 +5,17 @@ "type": "object", "properties": { "cp": { - "type": "integer", + "type": [ + "integer", + "string" + ], "description": "An ID which identifies the publisher selling the impression" }, "ct": { - "type": "integer", + "type": [ + "integer", + "string" + ], "description": "An ID which identifies the ad slot being sold" } }, diff --git a/static/bidder-params/qt.json b/static/bidder-params/qt.json new file mode 100644 index 00000000000..112ada92c18 --- /dev/null +++ b/static/bidder-params/qt.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "QT Adapter Params", + "description": "A schema which validates params accepted by the QT adapter", + "type": "object", + "properties": { + "placementId": { + "type": "string", + "minLength": 1, + "description": "Placement ID" + }, + "endpointId": { + "type": "string", + "minLength": 1, + "description": "Endpoint ID" + } + }, + "oneOf": [ + { "required": ["placementId"] }, + { "required": ["endpointId"] } + ] +} diff --git a/static/bidder-params/readpeak.json b/static/bidder-params/readpeak.json new file mode 100644 index 00000000000..1669ea749c4 --- /dev/null +++ b/static/bidder-params/readpeak.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Readpeak Adapter Params", + "description": "A schema which validates params accepted by the Readpeak adapter", + "type": "object", + "properties": { + "publisherId": { + "type": "string", + "description": "Publisher ID provided by Readpeak" + }, + "siteId": { + "type": "string", + "description": "Site/Media ID provided by Readpeak" + }, + "bidfloor": { + "type": "number", + "description": "CPM Bid Floor" + }, + "tagId": { + "type": "string", + "description": "Ad placement identifier" + } + }, + "required": ["publisherId"] +} diff --git a/static/bidder-params/relevantdigital.json b/static/bidder-params/relevantdigital.json new file mode 100644 index 00000000000..5f88d85fe58 --- /dev/null +++ b/static/bidder-params/relevantdigital.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Relevant Digital Adapter Params", + "description": "A schema which validates params accepted by the Relevant Digital adapter", + "type": "object", + "properties": { + "accountId": { + "type": "string", + "description": "An ID which identifies the Relevant Digital account ID" + }, + "placementId": { + "type": "string", + "description": "An ID which identifies the Relevant Digital placement ID" + }, + "pbsHost": { + "type": "string", + "description": "Prebid Server Host supplied by Relevant Digital" + }, + "pbsBufferMs": { + "type": "number", + "description": "TMax buffer, default is 250" + } + }, + "required": [ + "accountId", + "placementId", + "pbsHost" + ] +} diff --git a/static/bidder-params/rise.json b/static/bidder-params/rise.json index c5344b7ab0f..ee8a469cbbc 100644 --- a/static/bidder-params/rise.json +++ b/static/bidder-params/rise.json @@ -11,10 +11,22 @@ "publisher_id": { "type": "string", "description": "Deprecated, use org instead." + }, + "placementId": { + "type": "string", + "description": "Placement ID." } }, "oneOf": [ - { "required": ["org"] }, - { "required": ["publisher_id"] } + { + "required": [ + "org" + ] + }, + { + "required": [ + "publisher_id" + ] + } ] } diff --git a/static/bidder-params/roulax.json b/static/bidder-params/roulax.json new file mode 100644 index 00000000000..adcf847c0be --- /dev/null +++ b/static/bidder-params/roulax.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Roulax Adapter Params", + "description": "A schema which validates params accepted by the Roulax adapter", + "type": "object", + "properties": { + "PId": { + "type": "string", + "minLength": 1, + "description": "PID" + }, + "PublisherPath": { + "type": "string", + "minLength": 1, + "description": "PublisherPath" + } + }, + "required": ["Pid", "PublisherPath"] +} \ No newline at end of file diff --git a/static/bidder-params/seedingAlliance.json b/static/bidder-params/seedingAlliance.json index cf9aa375eb4..d72086230aa 100644 --- a/static/bidder-params/seedingAlliance.json +++ b/static/bidder-params/seedingAlliance.json @@ -8,6 +8,14 @@ "type": "string", "description": "Ad Unit ID", "minLength": 1 + }, + "seatId": { + "type": "string", + "description": "Deprecated, please use accountId" + }, + "accountId": { + "type": "string", + "description": "Account ID of partner" } }, "required": [ diff --git a/static/bidder-params/smrtconnect.json b/static/bidder-params/smrtconnect.json new file mode 100644 index 00000000000..6916d4567b0 --- /dev/null +++ b/static/bidder-params/smrtconnect.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Smrtconnect Params", + "description": "A schema which validates params accepted by the Smrtconnect", + "type": "object", + "properties": { + "supply_id": { + "type": "string", + "description": "Supply id", + "minLength": 1 + } + }, + "required": ["supply_id"] +} diff --git a/static/bidder-params/sovrnXsp.json b/static/bidder-params/sovrnXsp.json new file mode 100644 index 00000000000..beea3f588df --- /dev/null +++ b/static/bidder-params/sovrnXsp.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Sovrn XSP Adapter Params", + "description": "Schema validating params accepted by the Sovrn XSP adapter", + "type": "object", + "properties": { + "pub_id": { + "type": "string", + "description": "Assigned publisher ID", + "minLength": 4 + }, + "med_id": { + "type": "string", + "description": "Property ID not zone ID not provided" + }, + "zone_id": { + "type": "string", + "description": "Specific zone ID for this placement, belonging to app/site", + "minLength": 20 + }, + "force_bid": { + "type": "boolean", + "description": "Force bids with a test creative" + } + }, + "required": [ "pub_id" ] + } diff --git a/static/bidder-params/suntContent.json b/static/bidder-params/suntContent.json deleted file mode 100644 index e85ad3f5de5..00000000000 --- a/static/bidder-params/suntContent.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "SUNT Content Adapter Params", - "description": "A schema which validates params accepted by the SUNT Content adapter", - "type": "object", - "properties": { - "adUnitId": { - "type": "string", - "description": "Ad Unit ID", - "minLength": 1 - } - }, - "required": [ - "adUnitId" - ] -} \ No newline at end of file diff --git a/static/bidder-params/synacormedia.json b/static/bidder-params/synacormedia.json deleted file mode 100644 index 21eb06cc7ce..00000000000 --- a/static/bidder-params/synacormedia.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Synacormedia Adapter Params", - "description": "DEPRECATED: Use imds bidder instead. A schema which validates params accepted by the Synacormedia adapter", - - "type": "object", - "properties": { - "seatId": { - "type": "string", - "description": "The seat id." - }, - "tagId": { - "type": "string", - "description": "The tag id." - } - }, - - "required": ["seatId"] -} diff --git a/static/bidder-params/theadx.json b/static/bidder-params/theadx.json new file mode 100644 index 00000000000..cdbbf22f96b --- /dev/null +++ b/static/bidder-params/theadx.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Theadx Adapter Params", + "description": "A schema which validates params accepted by the theadx adapter", + "type": "object", + "properties": { + "pid": { + "type": ["integer", "string"], + "pattern": "^\\d+$", + "description": "An ID which identifies the partner selling the impression" + }, + "tagid": { + "type": ["integer", "string"], + "pattern": "^\\d+$", + "description": "An ID which identifies the placement selling the impression" + }, + "wid": { + "type": ["integer", "string"], + "description": "An ID which identifies the Theadx inventory source id" + } + + }, + "anyOf":[ + { + "required": ["tagid"] + } + , { + "required": ["pid", "wid","tagid"] + } + + ] +} diff --git a/static/bidder-params/thetradedesk.json b/static/bidder-params/thetradedesk.json new file mode 100644 index 00000000000..5a85cf2f516 --- /dev/null +++ b/static/bidder-params/thetradedesk.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "The Trade Desk Adapter Params", + "description": "A schema which validates params accepted by the The Trade Desk adapter", + "type": "object", + "properties": { + "publisherId": { + "type": "string", + "description": "An ID which identifies the publisher" + } + }, + "required": ["publisherId"] +} diff --git a/static/bidder-params/triplelift_native.json b/static/bidder-params/triplelift_native.json index 4fd71c722ba..4cf90ef49e7 100644 --- a/static/bidder-params/triplelift_native.json +++ b/static/bidder-params/triplelift_native.json @@ -7,6 +7,7 @@ "properties": { "inventoryCode": { "type": "string", + "minLength": 1, "description": "TripleLift inventory code for this ad unit (provided to you by your partner manager)" }, "floor" : {"description" : "the bid floor, in usd", "type": "number" } diff --git a/static/bidder-params/trustedstack.json b/static/bidder-params/trustedstack.json new file mode 100644 index 00000000000..b52e7bf3f7f --- /dev/null +++ b/static/bidder-params/trustedstack.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Trustedstack Adapter Params", + "description": "A schema which validates params accepted by the Trustedstack adapter", + "type": "object", + "properties": { + "cid": { + "type": "string", + "minLength": 1, + "description": "The customer id provided by Trustedstack." + }, + "crid": { + "type": "string", + "minLength": 1, + "description": "The placement id provided by Trustedstack." + } + }, + "required": [ + "cid", + "crid" + ] +} \ No newline at end of file diff --git a/static/bidder-params/trustx.json b/static/bidder-params/trustx.json deleted file mode 100644 index efedf9de537..00000000000 --- a/static/bidder-params/trustx.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "TrustX Adapter Params", - "description": "A schema which validates params accepted by TrustX adapter", - "type": "object", - "properties": { - "uid": { - "type": "integer", - "description": "An ID which identifies this placement of the impression" - } - }, - "required": [] -} diff --git a/static/bidder-params/vidazoo.json b/static/bidder-params/vidazoo.json new file mode 100644 index 00000000000..95683d73d3c --- /dev/null +++ b/static/bidder-params/vidazoo.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Vidazoo Adapter Params", + "description": "A schema which validates params accepted by the Vidazoo adapter", + "type": "object", + "properties": { + "cId": { + "type": "string", + "description": "The connection id.", + "minLength": 1 + } + }, + "required": [ + "cId" + ] +} \ No newline at end of file diff --git a/static/bidder-params/liftoff.json b/static/bidder-params/vungle.json similarity index 89% rename from static/bidder-params/liftoff.json rename to static/bidder-params/vungle.json index 32aa7f89a53..6ad85e17800 100644 --- a/static/bidder-params/liftoff.json +++ b/static/bidder-params/vungle.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Liftoff Adapter Params", - "description": "A schema which validates params accepted by the Liftoff adapter", + "title": "Vungle Adapter Params", + "description": "A schema which validates params accepted by the Vungle adapter", "type": "object", "properties": { "app_store_id": { diff --git a/static/bidder-params/yahooAdvertising.json b/static/bidder-params/yahooAdvertising.json deleted file mode 100644 index 4778f0778c7..00000000000 --- a/static/bidder-params/yahooAdvertising.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "YahooAdvertising Adapter Params", - "description": "A schema which validates params accepted by the YahooAdvertising adapter", - "type": "object", - "properties": { - "dcn": { - "type": "string", - "minLength": 1, - "description": "Site ID provided by One Mobile" - }, - "pos": { - "type": "string", - "minLength": 1, - "description": "Placement ID" - } - }, - "required": ["dcn", "pos"] -} diff --git a/static/bidder-params/yahoossp.json b/static/bidder-params/yahoossp.json deleted file mode 100644 index 9d971149339..00000000000 --- a/static/bidder-params/yahoossp.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "YahooSSP Adapter Params", - "description": "A schema which validates params accepted by the YahooSSP adapter", - "type": "object", - "properties": { - "dcn": { - "type": "string", - "minLength": 1, - "description": "Site ID provided by One Mobile" - }, - "pos": { - "type": "string", - "minLength": 1, - "description": "Placement ID" - } - }, - "required": ["dcn", "pos"] -} diff --git a/static/bidder-params/yandex.json b/static/bidder-params/yandex.json new file mode 100644 index 00000000000..24de473f31e --- /dev/null +++ b/static/bidder-params/yandex.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Yandex Adapter Params", + "description": "A schema which validates params accepted by the Yandex adapter", + "type": "object", + "properties": { + "page_id": { + "type": "integer", + "minLength": 1, + "minimum": 1, + "description": "Special Page Id provided by Yandex Manager" + }, + "imp_id": { + "type": "integer", + "minLength": 1, + "minimum": 1, + "description": "Special identifier provided by Yandex Manager" + }, + "placement_id": { + "type": "string", + "description": "Ad placement identifier", + "pattern": "(\\S+-)?\\d+-\\d+" + } + }, + "oneOf": [ + { + "required": [ "page_id", "imp_id" ] + }, + { + "required": [ "placement_id" ] + } + ] +} \ No newline at end of file diff --git a/static/bidder-params/zmaticoo.json b/static/bidder-params/zmaticoo.json new file mode 100644 index 00000000000..fc89e5bedf9 --- /dev/null +++ b/static/bidder-params/zmaticoo.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "zMaticoo Adapter Params", + "description": "A schema which validates params accepted by the zMaticoo adapter", + "type": "object", + "properties": { + "pubId": { + "type": "string", + "description": "Publisher ID", + "minLength": 1 + }, + "zoneId": { + "type": "string", + "description": "Zone Id", + "minLength": 1 + } + }, + "required": [ + "pubId", + "zoneId" + ] +} \ No newline at end of file diff --git a/static/index.html b/static/index.html index a3518ce27ae..ac79079f34f 100644 --- a/static/index.html +++ b/static/index.html @@ -3,7 +3,7 @@ Prebid Server - Prebid Server is a server-to-server proxy for Prebid.js users. + Prebid Server is a server-to-server proxy for Prebid.js users. The host is not responsible for the content or advertising delivered through this proxy. For more information, please contact prebid-server - at - prebid.org. diff --git a/stored_requests/backends/db_fetcher/fetcher.go b/stored_requests/backends/db_fetcher/fetcher.go index 3ea36bc0fc7..4fcebd3c6d8 100644 --- a/stored_requests/backends/db_fetcher/fetcher.go +++ b/stored_requests/backends/db_fetcher/fetcher.go @@ -7,8 +7,8 @@ import ( "github.com/lib/pq" "github.com/golang/glog" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/db_provider" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/stored_requests/backends/db_provider" ) func NewFetcher( @@ -152,7 +152,7 @@ func (fetcher *dbFetcher) FetchResponses(ctx context.Context, ids []string) (dat } func (fetcher *dbFetcher) FetchAccount(ctx context.Context, accountDefaultsJSON json.RawMessage, accountID string) (json.RawMessage, []error) { - return nil, []error{stored_requests.NotFoundError{accountID, "Account"}} + return nil, []error{stored_requests.NotFoundError{ID: accountID, DataType: "Account"}} } func (fetcher *dbFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) { diff --git a/stored_requests/backends/db_fetcher/fetcher_test.go b/stored_requests/backends/db_fetcher/fetcher_test.go index 04753fb8af5..6da09abf7b4 100644 --- a/stored_requests/backends/db_fetcher/fetcher_test.go +++ b/stored_requests/backends/db_fetcher/fetcher_test.go @@ -11,7 +11,7 @@ import ( "time" "github.com/DATA-DOG/go-sqlmock" - "github.com/prebid/prebid-server/stored_requests/backends/db_provider" + "github.com/prebid/prebid-server/v3/stored_requests/backends/db_provider" "github.com/stretchr/testify/assert" ) diff --git a/stored_requests/backends/db_provider/db_provider.go b/stored_requests/backends/db_provider/db_provider.go index 0f79a7737e0..01cd13b7b20 100644 --- a/stored_requests/backends/db_provider/db_provider.go +++ b/stored_requests/backends/db_provider/db_provider.go @@ -5,7 +5,7 @@ import ( "database/sql" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v3/config" ) type DbProvider interface { diff --git a/stored_requests/backends/db_provider/db_provider_mock.go b/stored_requests/backends/db_provider/db_provider_mock.go index 3d4cfda76c3..1b1c1ff6012 100644 --- a/stored_requests/backends/db_provider/db_provider_mock.go +++ b/stored_requests/backends/db_provider/db_provider_mock.go @@ -6,7 +6,7 @@ import ( "reflect" "github.com/DATA-DOG/go-sqlmock" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v3/config" ) func NewDbProviderMock() (*DbProviderMock, sqlmock.Sqlmock, error) { diff --git a/stored_requests/backends/db_provider/mysql_dbprovider.go b/stored_requests/backends/db_provider/mysql_dbprovider.go index 6301a119c45..bdcfacbae87 100644 --- a/stored_requests/backends/db_provider/mysql_dbprovider.go +++ b/stored_requests/backends/db_provider/mysql_dbprovider.go @@ -15,7 +15,7 @@ import ( "strings" "github.com/go-sql-driver/mysql" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v3/config" ) const customTLSKey = "prebid-tls" diff --git a/stored_requests/backends/db_provider/mysql_dbprovider_test.go b/stored_requests/backends/db_provider/mysql_dbprovider_test.go index e47280ef26b..c12d7f5abc3 100644 --- a/stored_requests/backends/db_provider/mysql_dbprovider_test.go +++ b/stored_requests/backends/db_provider/mysql_dbprovider_test.go @@ -6,7 +6,7 @@ import ( "runtime" "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v3/config" "github.com/stretchr/testify/assert" ) diff --git a/stored_requests/backends/db_provider/postgres_dbprovider.go b/stored_requests/backends/db_provider/postgres_dbprovider.go index ef945faebc9..c94632a0f3c 100644 --- a/stored_requests/backends/db_provider/postgres_dbprovider.go +++ b/stored_requests/backends/db_provider/postgres_dbprovider.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v3/config" ) type PostgresDbProvider struct { diff --git a/stored_requests/backends/db_provider/postgres_dbprovider_test.go b/stored_requests/backends/db_provider/postgres_dbprovider_test.go index 9e98c0b5763..1be77d0d283 100644 --- a/stored_requests/backends/db_provider/postgres_dbprovider_test.go +++ b/stored_requests/backends/db_provider/postgres_dbprovider_test.go @@ -4,7 +4,7 @@ import ( "errors" "testing" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v3/config" "github.com/stretchr/testify/assert" ) diff --git a/stored_requests/backends/empty_fetcher/fetcher.go b/stored_requests/backends/empty_fetcher/fetcher.go index 0246990c02e..405903e533f 100644 --- a/stored_requests/backends/empty_fetcher/fetcher.go +++ b/stored_requests/backends/empty_fetcher/fetcher.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v3/stored_requests" ) // EmptyFetcher is a nil-object which has no Stored Requests. @@ -33,7 +33,7 @@ func (fetcher EmptyFetcher) FetchResponses(ctx context.Context, ids []string) (d } func (fetcher EmptyFetcher) FetchAccount(ctx context.Context, accountDefaultJSON json.RawMessage, accountID string) (json.RawMessage, []error) { - return nil, []error{stored_requests.NotFoundError{accountID, "Account"}} + return nil, []error{stored_requests.NotFoundError{ID: accountID, DataType: "Account"}} } func (fetcher EmptyFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) { diff --git a/stored_requests/backends/file_fetcher/fetcher.go b/stored_requests/backends/file_fetcher/fetcher.go index a9bbe919dcf..1c51a353fb8 100644 --- a/stored_requests/backends/file_fetcher/fetcher.go +++ b/stored_requests/backends/file_fetcher/fetcher.go @@ -3,12 +3,13 @@ package file_fetcher import ( "context" "encoding/json" + "errors" "fmt" "os" "strings" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/util/jsonutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" ) @@ -35,8 +36,15 @@ func (fetcher *eagerFetcher) FetchRequests(ctx context.Context, requestIDs []str return storedRequests, storedImpressions, errs } +// Fetch Responses - Implements the interface to read the stored response information from the fetcher's FileSystem, the directory name is "stored_responses" func (fetcher *eagerFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) { - return nil, nil + storedRespFS, found := fetcher.FileSystem.Directories["stored_responses"] + if !found { + return nil, append(errs, errors.New(`no "stored_responses" directory found`)) + } + + data = storedRespFS.Files + return data, appendErrors("Response", ids, data, nil) } // FetchAccount fetches the host account configuration for a publisher diff --git a/stored_requests/backends/file_fetcher/fetcher_test.go b/stored_requests/backends/file_fetcher/fetcher_test.go index a69945641f1..80e45bb9445 100644 --- a/stored_requests/backends/file_fetcher/fetcher_test.go +++ b/stored_requests/backends/file_fetcher/fetcher_test.go @@ -6,17 +6,19 @@ import ( "fmt" "testing" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/util/jsonutil" "github.com/stretchr/testify/assert" ) func TestFileFetcher(t *testing.T) { + // Load the test input files for testing fetcher, err := NewFileFetcher("./test") if err != nil { t.Errorf("Failed to create a Fetcher: %v", err) } + // Test stored request and stored imps storedReqs, storedImps, errs := fetcher.FetchRequests(context.Background(), []string{"1", "2"}, []string{"some-imp"}) assertErrorCount(t, 0, errs) @@ -25,6 +27,54 @@ func TestFileFetcher(t *testing.T) { validateImp(t, storedImps) } +func TestStoredResponseFileFetcher(t *testing.T) { + // grab the fetcher that do not have /test/stored_responses/stored_responses FS directory + directoryNotExistfetcher, err := NewFileFetcher("./test/stored_responses") + if err != nil { + t.Errorf("Failed to create a Fetcher: %v", err) + } + + // we should receive 1 error since we do not have "stored_responses" directory in ./test/stored_responses + _, errs := directoryNotExistfetcher.FetchResponses(context.Background(), []string{}) + assertErrorCount(t, 1, errs) + + // grab the fetcher that has /test/stored_responses FS directory + fetcher, err := NewFileFetcher("./test") + if err != nil { + t.Errorf("Failed to create a Fetcher: %v", err) + } + + // Test stored responses, we have 3 stored responses in ./test/stored_responses + storedResps, errs := fetcher.FetchResponses(context.Background(), []string{"bar", "escaped", "does_not_exist"}) + // expect 1 error since we do not have "does_not_exist" stored response file from ./test + assertErrorCount(t, 1, errs) + + validateStoredResponse[map[string]string](t, storedResps, "bar", func(val map[string]string) error { + if len(val) != 1 { + return fmt.Errorf("Unexpected value length. Expected %d, Got %d", 1, len(val)) + } + + data, hadKey := val["test"] + if !hadKey { + return fmt.Errorf(`missing key "test" in the value`) + } + + expectedVal := "bar" + if data != expectedVal { + return fmt.Errorf(`Bad value for key "test". Expected "%s", Got "%s"`, expectedVal, data) + } + return nil + }) + + validateStoredResponse[string](t, storedResps, "escaped", func(val string) error { + expectedVal := `esca"ped` + if val != expectedVal { + return fmt.Errorf(`Bad data. Expected "%v", Got "%s"`, expectedVal, val) + } + return nil + }) +} + func TestAccountFetcher(t *testing.T) { fetcher, err := NewFileFetcher("./test") assert.NoError(t, err, "Failed to create test fetcher") @@ -36,7 +86,7 @@ func TestAccountFetcher(t *testing.T) { _, errs = fetcher.FetchAccount(context.Background(), json.RawMessage(`{"events_enabled":true}`), "nonexistent") assertErrorCount(t, 1, errs) assert.Error(t, errs[0]) - assert.Equal(t, stored_requests.NotFoundError{"nonexistent", "Account"}, errs[0]) + assert.Equal(t, stored_requests.NotFoundError{ID: "nonexistent", DataType: "Account"}, errs[0]) _, errs = fetcher.FetchAccount(context.Background(), json.RawMessage(`{"events_enabled"}`), "valid") assertErrorCount(t, 1, errs) @@ -135,7 +185,7 @@ func TestCategoriesFetcherWithPublisher(t *testing.T) { if err != nil { t.Errorf("Failed to create a category Fetcher: %v", err) } - category, err := fetcher.FetchCategories(nil, "test", "categories", "IAB1-1") + category, err := fetcher.FetchCategories(context.TODO(), "test", "categories", "IAB1-1") assert.Equal(t, nil, err, "Categories were loaded incorrectly") assert.Equal(t, "Beverages", category, "Categories were loaded incorrectly") } @@ -145,7 +195,7 @@ func TestCategoriesFetcherWithoutPublisher(t *testing.T) { if err != nil { t.Errorf("Failed to create a category Fetcher: %v", err) } - category, err := fetcher.FetchCategories(nil, "test", "", "IAB1-1") + category, err := fetcher.FetchCategories(context.TODO(), "test", "", "IAB1-1") assert.Equal(t, nil, err, "Categories were loaded incorrectly") assert.Equal(t, "VideoGames", category, "Categories were loaded incorrectly") } @@ -155,7 +205,7 @@ func TestCategoriesFetcherNoCategory(t *testing.T) { if err != nil { t.Errorf("Failed to create a category Fetcher: %v", err) } - _, fetchingErr := fetcher.FetchCategories(nil, "test", "", "IAB1-100") + _, fetchingErr := fetcher.FetchCategories(context.TODO(), "test", "", "IAB1-100") assert.Equal(t, fmt.Errorf("Unable to find category for adserver 'test', publisherId: '', iab category: 'IAB1-100'"), fetchingErr, "Categories were loaded incorrectly") } @@ -165,7 +215,7 @@ func TestCategoriesFetcherBrokenJson(t *testing.T) { if err != nil { t.Errorf("Failed to create a category Fetcher: %v", err) } - _, fetchingErr := fetcher.FetchCategories(nil, "test", "broken", "IAB1-100") + _, fetchingErr := fetcher.FetchCategories(context.TODO(), "test", "broken", "IAB1-100") assert.Equal(t, fmt.Errorf("Unable to unmarshal categories for adserver: 'test', publisherId: 'broken'"), fetchingErr, "Categories were loaded incorrectly") } @@ -175,7 +225,24 @@ func TestCategoriesFetcherNoCategoriesFile(t *testing.T) { if err != nil { t.Errorf("Failed to create a category Fetcher: %v", err) } - _, fetchingErr := fetcher.FetchCategories(nil, "test", "not_exists", "IAB1-100") + _, fetchingErr := fetcher.FetchCategories(context.TODO(), "test", "not_exists", "IAB1-100") assert.Equal(t, fmt.Errorf("Unable to find mapping file for adserver: 'test', publisherId: 'not_exists'"), fetchingErr, "Categories were loaded incorrectly") } + +// validateStoredResponse - reusable function in the stored response test to verify the actual data read from the fetcher +func validateStoredResponse[T any](t *testing.T, storedInfo map[string]json.RawMessage, id string, verifyFunc func(outputVal T) error) { + storedValue, hasID := storedInfo[id] + if !hasID { + t.Fatalf(`Expected stored response data to have id: "%s"`, id) + } + + var unmarshalledValue T + if err := jsonutil.UnmarshalValid(storedValue, &unmarshalledValue); err != nil { + t.Errorf(`Failed to unmarshal stored response data of id "%s": %v`, id, err) + } + + if err := verifyFunc(unmarshalledValue); err != nil { + t.Errorf(`Bad data in stored response of id: "%s": %v`, id, err) + } +} diff --git a/stored_requests/backends/file_fetcher/test/stored_responses/bar.json b/stored_requests/backends/file_fetcher/test/stored_responses/bar.json new file mode 100644 index 00000000000..e1f4c568f48 --- /dev/null +++ b/stored_requests/backends/file_fetcher/test/stored_responses/bar.json @@ -0,0 +1,4 @@ +{ + "test": "bar" +} + \ No newline at end of file diff --git a/stored_requests/backends/file_fetcher/test/stored_responses/escaped.json b/stored_requests/backends/file_fetcher/test/stored_responses/escaped.json new file mode 100644 index 00000000000..d302ea50709 --- /dev/null +++ b/stored_requests/backends/file_fetcher/test/stored_responses/escaped.json @@ -0,0 +1 @@ +"esca\"ped" diff --git a/stored_requests/backends/http_fetcher/fetcher.go b/stored_requests/backends/http_fetcher/fetcher.go index 88afa39fb1d..dc4dd03a1fe 100644 --- a/stored_requests/backends/http_fetcher/fetcher.go +++ b/stored_requests/backends/http_fetcher/fetcher.go @@ -9,8 +9,8 @@ import ( "net/url" "strings" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/util/jsonutil" jsonpatch "gopkg.in/evanphx/json-patch.v4" "github.com/golang/glog" @@ -207,6 +207,9 @@ func (fetcher *HttpFetcher) FetchCategories(ctx context.Context, primaryAdServer defer httpResp.Body.Close() respBytes, err := io.ReadAll(httpResp.Body) + if err != nil { + return "", fmt.Errorf("Unable to read response body: %v", err) + } tmp := make(map[string]stored_requests.Category) if err := jsonutil.UnmarshalValid(respBytes, &tmp); err != nil { diff --git a/stored_requests/backends/http_fetcher/fetcher_test.go b/stored_requests/backends/http_fetcher/fetcher_test.go index 80be6918ad8..9ba85eac948 100644 --- a/stored_requests/backends/http_fetcher/fetcher_test.go +++ b/stored_requests/backends/http_fetcher/fetcher_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/jsonutil" "github.com/stretchr/testify/assert" ) @@ -105,7 +105,7 @@ func TestFetchAccountsNoIDsProvided(t *testing.T) { fetcher, close := newTestAccountFetcher(t, []string{"acc-1", "acc-2"}) defer close() - accData, errs := fetcher.FetchAccounts(nil, []string{}) + accData, errs := fetcher.FetchAccounts(context.TODO(), []string{}) assert.Empty(t, errs, "Unexpected error fetching empty account list") assert.Nil(t, accData, "Fetching empty account list should return nil") } @@ -115,7 +115,7 @@ func TestFetchAccountsFailedBuildRequest(t *testing.T) { fetcher, close := newTestAccountFetcher(t, []string{"acc-1", "acc-2"}) defer close() - accData, errs := fetcher.FetchAccounts(nil, []string{"acc-1"}) + accData, errs := fetcher.FetchAccounts(nil, []string{"acc-1"}) //nolint: staticcheck // test handling of a nil context assert.Len(t, errs, 1, "Fetching accounts without context should result in error ") assert.Nil(t, accData, "Fetching accounts without context should return nil") } diff --git a/stored_requests/caches/cachestest/reliable.go b/stored_requests/caches/cachestest/reliable.go index 7fbaf7238af..f4042e28c4a 100644 --- a/stored_requests/caches/cachestest/reliable.go +++ b/stored_requests/caches/cachestest/reliable.go @@ -5,7 +5,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v3/stored_requests" ) const ( diff --git a/stored_requests/caches/memory/cache.go b/stored_requests/caches/memory/cache.go index 5939c26ddec..c7c9f7ceea8 100644 --- a/stored_requests/caches/memory/cache.go +++ b/stored_requests/caches/memory/cache.go @@ -7,7 +7,7 @@ import ( "github.com/coocood/freecache" "github.com/golang/glog" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v3/stored_requests" ) // NewCache returns an in-memory Cache which evicts items if: diff --git a/stored_requests/caches/memory/cache_test.go b/stored_requests/caches/memory/cache_test.go index b89bd5af26f..91548e46934 100644 --- a/stored_requests/caches/memory/cache_test.go +++ b/stored_requests/caches/memory/cache_test.go @@ -7,8 +7,8 @@ import ( "strconv" "testing" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/caches/cachestest" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/stored_requests/caches/cachestest" ) func TestLRURobustness(t *testing.T) { diff --git a/stored_requests/config/config.go b/stored_requests/config/config.go index 9cb349d1f72..6950c5ebc0c 100644 --- a/stored_requests/config/config.go +++ b/stored_requests/config/config.go @@ -5,24 +5,24 @@ import ( "net/http" "time" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v3/metrics" "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/db_fetcher" - "github.com/prebid/prebid-server/stored_requests/backends/db_provider" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/stored_requests/backends/file_fetcher" - "github.com/prebid/prebid-server/stored_requests/backends/http_fetcher" - "github.com/prebid/prebid-server/stored_requests/caches/memory" - "github.com/prebid/prebid-server/stored_requests/caches/nil_cache" - "github.com/prebid/prebid-server/stored_requests/events" - apiEvents "github.com/prebid/prebid-server/stored_requests/events/api" - databaseEvents "github.com/prebid/prebid-server/stored_requests/events/database" - httpEvents "github.com/prebid/prebid-server/stored_requests/events/http" - "github.com/prebid/prebid-server/util/task" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/stored_requests/backends/db_fetcher" + "github.com/prebid/prebid-server/v3/stored_requests/backends/db_provider" + "github.com/prebid/prebid-server/v3/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v3/stored_requests/backends/file_fetcher" + "github.com/prebid/prebid-server/v3/stored_requests/backends/http_fetcher" + "github.com/prebid/prebid-server/v3/stored_requests/caches/memory" + "github.com/prebid/prebid-server/v3/stored_requests/caches/nil_cache" + "github.com/prebid/prebid-server/v3/stored_requests/events" + apiEvents "github.com/prebid/prebid-server/v3/stored_requests/events/api" + databaseEvents "github.com/prebid/prebid-server/v3/stored_requests/events/database" + httpEvents "github.com/prebid/prebid-server/v3/stored_requests/events/http" + "github.com/prebid/prebid-server/v3/util/task" ) // CreateStoredRequests returns three things: diff --git a/stored_requests/config/config_test.go b/stored_requests/config/config_test.go index b06feea7d31..917e3e975cf 100644 --- a/stored_requests/config/config_test.go +++ b/stored_requests/config/config_test.go @@ -13,14 +13,14 @@ import ( sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/backends/db_provider" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/prebid/prebid-server/stored_requests/backends/http_fetcher" - "github.com/prebid/prebid-server/stored_requests/events" - httpEvents "github.com/prebid/prebid-server/stored_requests/events/http" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/stored_requests/backends/db_provider" + "github.com/prebid/prebid-server/v3/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/v3/stored_requests/backends/http_fetcher" + "github.com/prebid/prebid-server/v3/stored_requests/events" + httpEvents "github.com/prebid/prebid-server/v3/stored_requests/events/http" "github.com/stretchr/testify/mock" ) diff --git a/stored_requests/data/by_id/accounts/test.json b/stored_requests/data/by_id/accounts/test.json index 699f6bd1e57..a53f8997f37 100644 --- a/stored_requests/data/by_id/accounts/test.json +++ b/stored_requests/data/by_id/accounts/test.json @@ -49,4 +49,4 @@ } } } -} +} \ No newline at end of file diff --git a/stored_requests/events/api/api.go b/stored_requests/events/api/api.go index 30778f0e11a..c9f87e0ae7e 100644 --- a/stored_requests/events/api/api.go +++ b/stored_requests/events/api/api.go @@ -5,8 +5,8 @@ import ( "net/http" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/stored_requests/events" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/stored_requests/events" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type eventsAPI struct { diff --git a/stored_requests/events/api/api_test.go b/stored_requests/events/api/api_test.go index 67ba836cb00..c5d67a9b862 100644 --- a/stored_requests/events/api/api_test.go +++ b/stored_requests/events/api/api_test.go @@ -9,9 +9,9 @@ import ( "strings" "testing" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/caches/memory" - "github.com/prebid/prebid-server/stored_requests/events" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/stored_requests/caches/memory" + "github.com/prebid/prebid-server/v3/stored_requests/events" ) func TestGoodRequests(t *testing.T) { diff --git a/stored_requests/events/database/database.go b/stored_requests/events/database/database.go index 24eddf214eb..965922b2707 100644 --- a/stored_requests/events/database/database.go +++ b/stored_requests/events/database/database.go @@ -9,11 +9,11 @@ import ( "time" "github.com/golang/glog" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/stored_requests/backends/db_provider" - "github.com/prebid/prebid-server/stored_requests/events" - "github.com/prebid/prebid-server/util/timeutil" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/stored_requests/backends/db_provider" + "github.com/prebid/prebid-server/v3/stored_requests/events" + "github.com/prebid/prebid-server/v3/util/timeutil" ) func bytesNull() []byte { @@ -78,7 +78,7 @@ func (e *DatabaseEventProducer) Invalidations() <-chan events.Invalidation { } func (e *DatabaseEventProducer) fetchAll() (fetchErr error) { - timeout := e.cfg.CacheInitTimeout * time.Millisecond + timeout := e.cfg.CacheInitTimeout ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() @@ -115,7 +115,7 @@ func (e *DatabaseEventProducer) fetchAll() (fetchErr error) { } func (e *DatabaseEventProducer) fetchDelta() (fetchErr error) { - timeout := e.cfg.CacheUpdateTimeout * time.Millisecond + timeout := e.cfg.CacheUpdateTimeout ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() diff --git a/stored_requests/events/database/database_test.go b/stored_requests/events/database/database_test.go index 8ce21bfde95..ffe02851cad 100644 --- a/stored_requests/events/database/database_test.go +++ b/stored_requests/events/database/database_test.go @@ -7,10 +7,10 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/stored_requests/backends/db_provider" - "github.com/prebid/prebid-server/stored_requests/events" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/stored_requests/backends/db_provider" + "github.com/prebid/prebid-server/v3/stored_requests/events" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" diff --git a/stored_requests/events/events.go b/stored_requests/events/events.go index 725df4279d8..e96692826b4 100644 --- a/stored_requests/events/events.go +++ b/stored_requests/events/events.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" - "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/v3/stored_requests" ) // Save represents a bulk save diff --git a/stored_requests/events/events_test.go b/stored_requests/events/events_test.go index 580f1eddf37..4520a332552 100644 --- a/stored_requests/events/events_test.go +++ b/stored_requests/events/events_test.go @@ -7,8 +7,8 @@ import ( "reflect" "testing" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/stored_requests/caches/memory" + "github.com/prebid/prebid-server/v3/stored_requests" + "github.com/prebid/prebid-server/v3/stored_requests/caches/memory" ) func TestListen(t *testing.T) { diff --git a/stored_requests/events/http/http.go b/stored_requests/events/http/http.go index be5b85a03a6..6ea41a6f848 100644 --- a/stored_requests/events/http/http.go +++ b/stored_requests/events/http/http.go @@ -12,8 +12,8 @@ import ( "golang.org/x/net/context/ctxhttp" "github.com/buger/jsonparser" - "github.com/prebid/prebid-server/stored_requests/events" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/stored_requests/events" + "github.com/prebid/prebid-server/v3/util/jsonutil" "github.com/golang/glog" ) @@ -109,58 +109,55 @@ func (e *HTTPEvents) fetchAll() { } func (e *HTTPEvents) refresh(ticker <-chan time.Time) { - for { - select { - case thisTime := <-ticker: - thisTimeInUTC := thisTime.UTC() - - // Parse the endpoint url defined - endpointUrl, urlErr := url.Parse(e.Endpoint) - - // Error with url parsing - if urlErr != nil { - glog.Errorf("Disabling refresh HTTP cache from GET '%s': %v", e.Endpoint, urlErr) - return - } + for thisTime := range ticker { + thisTimeInUTC := thisTime.UTC() - // Parse the url query string - urlQuery := endpointUrl.Query() + // Parse the endpoint url defined + endpointUrl, urlErr := url.Parse(e.Endpoint) - // See the last-modified query param - urlQuery.Set("last-modified", e.lastUpdate.Format(time.RFC3339)) + // Error with url parsing + if urlErr != nil { + glog.Errorf("Disabling refresh HTTP cache from GET '%s': %v", e.Endpoint, urlErr) + return + } - // Rebuild - endpointUrl.RawQuery = urlQuery.Encode() + // Parse the url query string + urlQuery := endpointUrl.Query() - // Convert to string - endpoint := endpointUrl.String() + // See the last-modified query param + urlQuery.Set("last-modified", e.lastUpdate.Format(time.RFC3339)) - glog.Infof("Refreshing HTTP cache from GET '%s'", endpoint) + // Rebuild + endpointUrl.RawQuery = urlQuery.Encode() - ctx, cancel := e.ctxProducer() - resp, err := ctxhttp.Get(ctx, e.client, endpoint) - if respObj, ok := e.parse(endpoint, resp, err); ok { - invalidations := events.Invalidation{ - Requests: extractInvalidations(respObj.StoredRequests), - Imps: extractInvalidations(respObj.StoredImps), - Responses: extractInvalidations(respObj.StoredResponses), - Accounts: extractInvalidations(respObj.Accounts), - } - if len(respObj.StoredRequests) > 0 || len(respObj.StoredImps) > 0 || len(respObj.StoredResponses) > 0 || len(respObj.Accounts) > 0 { - e.saves <- events.Save{ - Requests: respObj.StoredRequests, - Imps: respObj.StoredImps, - Responses: respObj.StoredResponses, - Accounts: respObj.Accounts, - } - } - if len(invalidations.Requests) > 0 || len(invalidations.Imps) > 0 || len(invalidations.Responses) > 0 || len(invalidations.Accounts) > 0 { - e.invalidations <- invalidations + // Convert to string + endpoint := endpointUrl.String() + + glog.Infof("Refreshing HTTP cache from GET '%s'", endpoint) + + ctx, cancel := e.ctxProducer() + resp, err := ctxhttp.Get(ctx, e.client, endpoint) + if respObj, ok := e.parse(endpoint, resp, err); ok { + invalidations := events.Invalidation{ + Requests: extractInvalidations(respObj.StoredRequests), + Imps: extractInvalidations(respObj.StoredImps), + Responses: extractInvalidations(respObj.StoredResponses), + Accounts: extractInvalidations(respObj.Accounts), + } + if len(respObj.StoredRequests) > 0 || len(respObj.StoredImps) > 0 || len(respObj.StoredResponses) > 0 || len(respObj.Accounts) > 0 { + e.saves <- events.Save{ + Requests: respObj.StoredRequests, + Imps: respObj.StoredImps, + Responses: respObj.StoredResponses, + Accounts: respObj.Accounts, } - e.lastUpdate = thisTimeInUTC } - cancel() + if len(invalidations.Requests) > 0 || len(invalidations.Imps) > 0 || len(invalidations.Responses) > 0 || len(invalidations.Accounts) > 0 { + e.invalidations <- invalidations + } + e.lastUpdate = thisTimeInUTC } + cancel() } } diff --git a/stored_requests/events/http/http_test.go b/stored_requests/events/http/http_test.go index 663d8cd193f..90fd61371c1 100644 --- a/stored_requests/events/http/http_test.go +++ b/stored_requests/events/http/http_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/jsonutil" "github.com/stretchr/testify/assert" ) diff --git a/stored_requests/fetcher.go b/stored_requests/fetcher.go index 433b33427b8..343e1f82d5f 100644 --- a/stored_requests/fetcher.go +++ b/stored_requests/fetcher.go @@ -5,7 +5,7 @@ import ( "encoding/json" "fmt" - "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/v3/metrics" ) // Fetcher knows how to fetch Stored Request data by id. diff --git a/stored_requests/fetcher_test.go b/stored_requests/fetcher_test.go index 684b867165c..c97340cbdb9 100644 --- a/stored_requests/fetcher_test.go +++ b/stored_requests/fetcher_test.go @@ -6,8 +6,8 @@ import ( "errors" "testing" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/stored_requests/caches/nil_cache" + "github.com/prebid/prebid-server/v3/metrics" + "github.com/prebid/prebid-server/v3/stored_requests/caches/nil_cache" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" diff --git a/stored_responses/stored_responses.go b/stored_responses/stored_responses.go index ef8fe50619d..6c8d4ae5f6b 100644 --- a/stored_responses/stored_responses.go +++ b/stored_responses/stored_responses.go @@ -4,12 +4,11 @@ import ( "context" "encoding/json" "fmt" + "strings" - "github.com/buger/jsonparser" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/stored_requests" ) type ImpsWithAuctionResponseIDs map[string]string @@ -23,22 +22,9 @@ type ImpBidderReplaceImpID map[string]map[string]bool type BidderImpReplaceImpID map[string]map[string]bool func InitStoredBidResponses(req *openrtb2.BidRequest, storedBidResponses ImpBidderStoredResp) BidderImpsWithBidResponses { - removeImpsWithStoredResponses(req, storedBidResponses) return buildStoredResp(storedBidResponses) } -// removeImpsWithStoredResponses deletes imps with stored bid resp -func removeImpsWithStoredResponses(req *openrtb2.BidRequest, storedBidResponses ImpBidderStoredResp) { - imps := req.Imp - req.Imp = nil //to indicate this bidder doesn't have real requests - for _, imp := range imps { - if _, ok := storedBidResponses[imp.ID]; !ok { - //add real imp back to request - req.Imp = append(req.Imp, imp) - } - } -} - func buildStoredResp(storedBidResponses ImpBidderStoredResp) BidderImpsWithBidResponses { // bidder -> imp id -> stored bid resp bidderToImpToResponses := BidderImpsWithBidResponses{} @@ -57,8 +43,7 @@ func buildStoredResp(storedBidResponses ImpBidderStoredResp) BidderImpsWithBidRe return bidderToImpToResponses } -func extractStoredResponsesIds(impInfo []ImpExtPrebidData, - bidderMap map[string]openrtb_ext.BidderName) ( +func extractStoredResponsesIds(impInfo []*openrtb_ext.ImpWrapper) ( StoredResponseIDs, ImpBiddersWithBidResponseIDs, ImpsWithAuctionResponseIDs, @@ -76,53 +61,62 @@ func extractStoredResponsesIds(impInfo []ImpExtPrebidData, impBidderReplaceImp := ImpBidderReplaceImpID{} for index, impData := range impInfo { - impId, err := jsonparser.GetString(impData.Imp, "id") + impId := impData.ID + impExt, err := impData.GetImpExt() if err != nil { - return nil, nil, nil, nil, fmt.Errorf("request.imp[%d] missing required field: \"id\"", index) + return nil, nil, nil, nil, err + } + impExtPrebid := impExt.GetPrebid() + if impExtPrebid == nil { + continue } - if impData.ImpExtPrebid.StoredAuctionResponse != nil { - if len(impData.ImpExtPrebid.StoredAuctionResponse.ID) == 0 { + if impExtPrebid.StoredAuctionResponse != nil { + if len(impExtPrebid.StoredAuctionResponse.ID) == 0 { return nil, nil, nil, nil, fmt.Errorf("request.imp[%d] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ", index) } - allStoredResponseIDs = append(allStoredResponseIDs, impData.ImpExtPrebid.StoredAuctionResponse.ID) + allStoredResponseIDs = append(allStoredResponseIDs, impExtPrebid.StoredAuctionResponse.ID) - impAuctionResponseIDs[impId] = impData.ImpExtPrebid.StoredAuctionResponse.ID + impAuctionResponseIDs[impId] = impExtPrebid.StoredAuctionResponse.ID } - if len(impData.ImpExtPrebid.StoredBidResponse) > 0 { + if len(impExtPrebid.StoredBidResponse) > 0 { + + // bidders can be specified in imp.ext and in imp.ext.prebid.bidders + allBidderNames := make([]string, 0) + for bidderName := range impExtPrebid.Bidder { + allBidderNames = append(allBidderNames, bidderName) + } + for extData := range impExt.GetExt() { + // no bidders will not be processed + allBidderNames = append(allBidderNames, extData) + } bidderStoredRespId := make(map[string]string) bidderReplaceImpId := make(map[string]bool) - for _, bidderResp := range impData.ImpExtPrebid.StoredBidResponse { + for _, bidderResp := range impExtPrebid.StoredBidResponse { if len(bidderResp.ID) == 0 || len(bidderResp.Bidder) == 0 { return nil, nil, nil, nil, fmt.Errorf("request.imp[%d] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ", index) } - bidderName := bidderResp.Bidder - normalizedCoreBidder, ok := openrtb_ext.NormalizeBidderName(bidderResp.Bidder) - if ok { - bidderName = normalizedCoreBidder.String() - } - //check if bidder is valid/exists - if _, isValid := bidderMap[bidderName]; !isValid { - return nil, nil, nil, nil, fmt.Errorf("request.imp[impId: %s].ext.prebid.bidder contains unknown bidder: %s. Did you forget an alias in request.ext.prebid.aliases?", impId, bidderResp.Bidder) - } - // bidder is unique per one bid stored response - // if more than one bidder specified the last defined bidder id will take precedence - bidderStoredRespId[bidderResp.Bidder] = bidderResp.ID - impBiddersWithBidResponseIDs[impId] = bidderStoredRespId - - // stored response config can specify if imp id should be replaced with imp id from request - replaceImpId := true - if bidderResp.ReplaceImpId != nil { - // replaceimpid is true if not specified - replaceImpId = *bidderResp.ReplaceImpId - } - bidderReplaceImpId[bidderResp.Bidder] = replaceImpId - impBidderReplaceImp[impId] = bidderReplaceImpId - //storedAuctionResponseIds are not unique, but fetch will return single data for repeated ids - allStoredResponseIDs = append(allStoredResponseIDs, bidderResp.ID) + for _, bidderName := range allBidderNames { + if _, found := bidderStoredRespId[bidderName]; !found && strings.EqualFold(bidderName, bidderResp.Bidder) { + bidderStoredRespId[bidderName] = bidderResp.ID + impBiddersWithBidResponseIDs[impId] = bidderStoredRespId + + // stored response config can specify if imp id should be replaced with imp id from request + replaceImpId := true + if bidderResp.ReplaceImpId != nil { + // replaceimpid is true if not specified + replaceImpId = *bidderResp.ReplaceImpId + } + bidderReplaceImpId[bidderName] = replaceImpId + impBidderReplaceImp[impId] = bidderReplaceImpId + + //storedAuctionResponseIds are not unique, but fetch will return single data for repeated ids + allStoredResponseIDs = append(allStoredResponseIDs, bidderResp.ID) + } + } } } } @@ -135,14 +129,11 @@ func extractStoredResponsesIds(impInfo []ImpExtPrebidData, // Note that processStoredResponses must be called after processStoredRequests // because stored imps and stored requests can contain stored auction responses and stored bid responses // so the stored requests/imps have to be merged into the incoming request prior to processing stored auction responses. -func ProcessStoredResponses(ctx context.Context, requestJson []byte, storedRespFetcher stored_requests.Fetcher, bidderMap map[string]openrtb_ext.BidderName) (ImpsWithBidResponses, ImpBidderStoredResp, BidderImpReplaceImpID, []error) { - impInfo, errs := parseImpInfo(requestJson) - if len(errs) > 0 { - return nil, nil, nil, errs - } - storedResponsesIds, impBidderToStoredBidResponseId, impIdToRespId, impBidderReplaceImp, err := extractStoredResponsesIds(impInfo, bidderMap) +func ProcessStoredResponses(ctx context.Context, requestWrapper *openrtb_ext.RequestWrapper, storedRespFetcher stored_requests.Fetcher) (ImpsWithBidResponses, ImpBidderStoredResp, BidderImpReplaceImpID, []error) { + + storedResponsesIds, impBidderToStoredBidResponseId, impIdToRespId, impBidderReplaceImp, err := extractStoredResponsesIds(requestWrapper.GetImp()) if err != nil { - return nil, nil, nil, append(errs, err) + return nil, nil, nil, []error{err} } if len(storedResponsesIds) > 0 { @@ -201,28 +192,3 @@ func buildStoredResponsesMaps(storedResponses StoredResponseIdToStoredResponse, } return impIdToStoredResp, impBidderToStoredBidResponse, errs } - -// parseImpInfo parses the request JSON and returns the impressions with their unmarshalled imp.ext.prebid -// copied from exchange to isolate stored responses code from auction dependencies -func parseImpInfo(requestJson []byte) (impData []ImpExtPrebidData, errs []error) { - - if impArray, dataType, _, err := jsonparser.Get(requestJson, "imp"); err == nil && dataType == jsonparser.Array { - _, err = jsonparser.ArrayEach(impArray, func(imp []byte, _ jsonparser.ValueType, _ int, err error) { - impExtData, _, _, err := jsonparser.Get(imp, "ext", "prebid") - var impExtPrebid openrtb_ext.ExtImpPrebid - if impExtData != nil { - if err := jsonutil.Unmarshal(impExtData, &impExtPrebid); err != nil { - errs = append(errs, err) - } - } - newImpData := ImpExtPrebidData{imp, impExtPrebid} - impData = append(impData, newImpData) - }) - } - return -} - -type ImpExtPrebidData struct { - Imp json.RawMessage - ImpExtPrebid openrtb_ext.ExtImpPrebid -} diff --git a/stored_responses/stored_responses_test.go b/stored_responses/stored_responses_test.go index c4ddea278a7..0dcd5e9a6de 100644 --- a/stored_responses/stored_responses_test.go +++ b/stored_responses/stored_responses_test.go @@ -6,77 +6,11 @@ import ( "errors" "testing" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) -func TestRemoveImpsWithStoredResponses(t *testing.T) { - bidRespId1 := json.RawMessage(`{"id": "resp_id1"}`) - testCases := []struct { - description string - reqIn *openrtb2.BidRequest - storedBidResponses ImpBidderStoredResp - expectedImps []openrtb2.Imp - }{ - { - description: "request with imps and stored bid response for this imp", - reqIn: &openrtb2.BidRequest{Imp: []openrtb2.Imp{ - {ID: "imp-id1"}, - }}, - storedBidResponses: ImpBidderStoredResp{ - "imp-id1": {"appnexus": bidRespId1}, - }, - expectedImps: nil, - }, - { - description: "request with imps and stored bid response for one of these imp", - reqIn: &openrtb2.BidRequest{Imp: []openrtb2.Imp{ - {ID: "imp-id1"}, - {ID: "imp-id2"}, - }}, - storedBidResponses: ImpBidderStoredResp{ - "imp-id1": {"appnexus": bidRespId1}, - }, - expectedImps: []openrtb2.Imp{ - { - ID: "imp-id2", - }, - }, - }, - { - description: "request with imps and stored bid response for both of these imp", - reqIn: &openrtb2.BidRequest{Imp: []openrtb2.Imp{ - {ID: "imp-id1"}, - {ID: "imp-id2"}, - }}, - storedBidResponses: ImpBidderStoredResp{ - "imp-id1": {"appnexus": bidRespId1}, - "imp-id2": {"appnexus": bidRespId1}, - }, - expectedImps: nil, - }, - { - description: "request with imps and no stored bid responses", - reqIn: &openrtb2.BidRequest{Imp: []openrtb2.Imp{ - {ID: "imp-id1"}, - {ID: "imp-id2"}, - }}, - storedBidResponses: nil, - - expectedImps: []openrtb2.Imp{ - {ID: "imp-id1"}, - {ID: "imp-id2"}, - }, - }, - } - for _, testCase := range testCases { - request := testCase.reqIn - removeImpsWithStoredResponses(request, testCase.storedBidResponses) - assert.Equal(t, testCase.expectedImps, request.Imp, "incorrect Impressions for testCase %s", testCase.description) - } -} - func TestBuildStoredBidResponses(t *testing.T) { bidRespId1 := json.RawMessage(`{"id": "resp_id1"}`) bidRespId2 := json.RawMessage(`{"id": "resp_id2"}`) @@ -163,136 +97,109 @@ func TestBuildStoredBidResponses(t *testing.T) { } func TestProcessStoredAuctionAndBidResponsesErrors(t *testing.T) { - bidderMap := map[string]openrtb_ext.BidderName{"testBidder": "testBidder"} - testCases := []struct { description string - requestJson []byte + request openrtb2.BidRequest expectedErrorList []error }{ { description: "Invalid stored auction response format: empty stored Auction Response Id", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "prebid": { - "storedauctionresponse": { - } - } - } - } - ]}`), + "storedauctionresponse": {} + }}`)}, + }, + }, expectedErrorList: []error{errors.New("request.imp[0] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ")}, }, { description: "Invalid stored bid response format: empty storedbidresponse.bidder", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "prebid": { "storedbidresponse": [ { "id": "123abc"}] - } - } - } - ]}`), + }}`)}, + }, + }, expectedErrorList: []error{errors.New("request.imp[0] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ")}, }, { description: "Invalid stored bid response format: empty storedbidresponse.id", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "prebid": { "storedbidresponse": [ { "bidder": "testbidder"}] - } - } - } - ]}`), + }}`)}, + }, + }, expectedErrorList: []error{errors.New("request.imp[0] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ")}, }, - { - description: "Invalid stored bid response: storedbidresponse.bidder not found", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "prebid": { - "storedbidresponse": [ - { "bidder": "testBidder123", "id": "123abc"}] - } - } - } - ]}`), - expectedErrorList: []error{errors.New("request.imp[impId: imp-id1].ext.prebid.bidder contains unknown bidder: testBidder123. Did you forget an alias in request.ext.prebid.aliases?")}, - }, { description: "Invalid stored auction response format: empty stored Auction Response Id in second imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "prebid": { "storedauctionresponse": { "id":"123" } - } - } - }, - { - "id": "imp-id2", - "ext": { + }}`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ "prebid": { - "storedauctionresponse": { + "storedauctionresponse": { "id":"" } - } - } - } - ]}`), + }}`)}, + }, + }, expectedErrorList: []error{errors.New("request.imp[1] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ")}, }, { description: "Invalid stored bid response format: empty stored bid Response Id in second imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "prebid": { - "storedbidresponse": [ + "storedbidresponse": [ {"bidder":"testBidder", "id": "123abc"} ] - } - } - }, - { - "id": "imp-id2", - "ext": { + }}`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ "prebid": { - "storedbidresponse": [ + "storedbidresponse": [ {"bidder":"testBidder", "id": ""} ] - } - } - } - ]}`), + }}`)}, + }, + }, expectedErrorList: []error{errors.New("request.imp[1] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ")}, }, } for _, test := range testCases { - _, _, _, errorList := ProcessStoredResponses(nil, test.requestJson, nil, bidderMap) - assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description) + t.Run(test.description, func(t *testing.T) { + rw := &openrtb_ext.RequestWrapper{BidRequest: &test.request} + _, _, _, errorList := ProcessStoredResponses(context.TODO(), rw, nil) + assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description) + }) } } func TestProcessStoredAuctionAndBidResponses(t *testing.T) { - bidderMap := map[string]openrtb_ext.BidderName{"bidderA": "bidderA", "bidderB": "bidderB"} bidStoredResp1 := json.RawMessage(`[{"bid": [{"id": "bid_id1"],"seat": "bidderA"}]`) bidStoredResp2 := json.RawMessage(`[{"bid": [{"id": "bid_id2"],"seat": "bidderB"}]`) bidStoredResp3 := json.RawMessage(`[{"bid": [{"id": "bid_id3"],"seat": "bidderA"}]`) @@ -305,33 +212,31 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { testCases := []struct { description string - requestJson []byte + request openrtb2.BidRequest expectedStoredAuctionResponses ImpsWithBidResponses expectedStoredBidResponses ImpBidderStoredResp expectedBidderImpReplaceImpID BidderImpReplaceImpID }{ { description: "No stored responses", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "prebid": { - - } - } - } - ]}`), + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "prebid": {} + }`)}, + }, + }, expectedStoredAuctionResponses: nil, expectedStoredBidResponses: nil, expectedBidderImpReplaceImpID: nil, }, { description: "Stored auction response one imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -340,9 +245,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "1" } } - } - } - ]}`), + }`)}, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{ "imp-id1": bidStoredResp1, }, @@ -351,11 +256,11 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { }, { description: "Stored bid response one imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { "placementId": 123 }, "prebid": { @@ -363,9 +268,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { {"bidder":"bidderA", "id": "1"} ] } - } - } - ]}`), + }`)}, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{}, expectedStoredBidResponses: ImpBidderStoredResp{ "imp-id1": {"bidderA": bidStoredResp1}, @@ -376,11 +281,14 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { }, { description: "Stored bid responses two bidders one imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "bidderB": { "placementId": 123 }, "prebid": { @@ -389,9 +297,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { {"bidder":"bidderB", "id": "2", "replaceimpid": false} ] } - } - } - ]}`), + }`)}, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{}, expectedStoredBidResponses: ImpBidderStoredResp{ "imp-id1": {"bidderA": bidStoredResp1, "bidderB": bidStoredResp2}, @@ -401,14 +309,120 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "bidderB": map[string]bool{"imp-id1": false}, }, }, + { + description: "Stored bid responses two same mixed case bidders one imp", + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "BIDDERa": { + "placementId": 123 + }, + "prebid": { + "storedbidresponse": [ + {"bidder":"bidderA", "id": "1", "replaceimpid": true}, + {"bidder":"bidderB", "id": "2", "replaceimpid": false} + ] + } + }`)}, + }, + }, + expectedStoredAuctionResponses: ImpsWithBidResponses{}, + expectedStoredBidResponses: ImpBidderStoredResp{ + "imp-id1": {"bidderA": bidStoredResp1, "BIDDERa": bidStoredResp1}, + }, + expectedBidderImpReplaceImpID: BidderImpReplaceImpID{ + "BIDDERa": map[string]bool{"imp-id1": true}, + "bidderA": map[string]bool{"imp-id1": true}, + }, + }, + { + description: "Stored bid responses 3 same mixed case bidders in imp.ext and imp.ext.prebid.bidders one imp", + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "BIDDERa": { + "placementId": 123 + }, + "prebid": { + "bidder": { + "BiddeRa": { + "placementId": 12883451 + } + }, + "storedbidresponse": [ + {"bidder":"bidderA", "id": "1", "replaceimpid": true}, + {"bidder":"bidderB", "id": "2", "replaceimpid": false} + ] + } + }`)}, + }, + }, + expectedStoredAuctionResponses: ImpsWithBidResponses{}, + expectedStoredBidResponses: ImpBidderStoredResp{ + "imp-id1": {"bidderA": bidStoredResp1, "BIDDERa": bidStoredResp1, "BiddeRa": bidStoredResp1}, + }, + expectedBidderImpReplaceImpID: BidderImpReplaceImpID{ + "BIDDERa": map[string]bool{"imp-id1": true}, + "bidderA": map[string]bool{"imp-id1": true}, + "BiddeRa": map[string]bool{"imp-id1": true}, + }, + }, + { + description: "Stored bid responses 3 same mixed case bidders in imp.ext and imp.ext.prebid.bidders one imp, duplicated stored response", + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "BIDDERa": { + "placementId": 123 + }, + "prebid": { + "bidder": { + "BiddeRa": { + "placementId": 12883451 + } + }, + "storedbidresponse": [ + {"bidder":"bidderA", "id": "1", "replaceimpid": true}, + {"bidder":"bidderA", "id": "2", "replaceimpid": true}, + {"bidder":"bidderB", "id": "2", "replaceimpid": false} + ] + } + }`)}, + }, + }, + expectedStoredAuctionResponses: ImpsWithBidResponses{}, + expectedStoredBidResponses: ImpBidderStoredResp{ + "imp-id1": {"bidderA": bidStoredResp1, "BIDDERa": bidStoredResp1, "BiddeRa": bidStoredResp1}, + }, + expectedBidderImpReplaceImpID: BidderImpReplaceImpID{ + "BIDDERa": map[string]bool{"imp-id1": true}, + "bidderA": map[string]bool{"imp-id1": true}, + "BiddeRa": map[string]bool{"imp-id1": true}, + }, + }, { //This is not a valid scenario for real auction request, added for testing purposes description: "Stored auction and bid responses one imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "bidderB": { "placementId": 123 }, "prebid": { @@ -420,9 +434,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { {"bidder":"bidderB", "id": "2"} ] } - } - } - ]}`), + }`)}, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{ "imp-id1": bidStoredResp1, }, @@ -436,10 +450,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { }, { description: "Stored auction response three imps", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -448,11 +462,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "1" } } - } - }, - { - "id": "imp-id2", - "ext": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -461,11 +473,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "2" } } - } - }, + }`)}, { - "id": "imp-id3", - "ext": { + ID: "imp-id3", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -474,9 +485,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "3" } } - } - } - ]}`), + }`), + }, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{ "imp-id1": bidStoredResp1, "imp-id2": bidStoredResp2, @@ -487,10 +499,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { }, { description: "Stored auction response three imps duplicated stored auction response", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -499,11 +511,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "1" } } - } - }, - { - "id": "imp-id2", - "ext": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -512,11 +522,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "2" } } - } - }, + }`)}, { - "id": "imp-id3", - "ext": { + ID: "imp-id3", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -525,9 +534,10 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { "id": "2" } } - } - } - ]}`), + }`), + }, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{ "imp-id1": bidStoredResp1, "imp-id2": bidStoredResp2, @@ -538,11 +548,14 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { }, { description: "Stored bid responses two bidders two imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "bidderB": { "placementId": 123 }, "prebid": { @@ -551,12 +564,13 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { {"bidder":"bidderB", "id": "2"} ] } - } - }, - { - "id": "imp-id2", - "ext": { - "appnexus": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ + "bidderA": { + "placementId": 123 + }, + "bidderB": { "placementId": 123 }, "prebid": { @@ -565,9 +579,9 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { {"bidder":"bidderB", "id": "2", "replaceimpid": false} ] } - } - } - ]}`), + }`)}, + }, + }, expectedStoredAuctionResponses: ImpsWithBidResponses{}, expectedStoredBidResponses: ImpBidderStoredResp{ "imp-id1": {"bidderA": bidStoredResp1, "bidderB": bidStoredResp2}, @@ -581,17 +595,19 @@ func TestProcessStoredAuctionAndBidResponses(t *testing.T) { } for _, test := range testCases { - storedAuctionResponses, storedBidResponses, bidderImpReplaceImpId, errorList := ProcessStoredResponses(nil, test.requestJson, fetcher, bidderMap) - assert.Equal(t, test.expectedStoredAuctionResponses, storedAuctionResponses, "storedAuctionResponses doesn't match: %s\n", test.description) - assert.Equalf(t, test.expectedStoredBidResponses, storedBidResponses, "storedBidResponses doesn't match: %s\n", test.description) - assert.Equal(t, test.expectedBidderImpReplaceImpID, bidderImpReplaceImpId, "bidderImpReplaceImpId doesn't match: %s\n", test.description) - assert.Nil(t, errorList, "Error should be nil") + t.Run(test.description, func(t *testing.T) { + rw := openrtb_ext.RequestWrapper{BidRequest: &test.request} + storedAuctionResponses, storedBidResponses, bidderImpReplaceImpId, errorList := ProcessStoredResponses(context.TODO(), &rw, fetcher) + assert.Equal(t, test.expectedStoredAuctionResponses, storedAuctionResponses) + assert.Equal(t, test.expectedStoredBidResponses, storedBidResponses) + assert.Equal(t, test.expectedBidderImpReplaceImpID, bidderImpReplaceImpId) + assert.Nil(t, errorList, "Error should be nil") + }) } } func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { - bidderMap := map[string]openrtb_ext.BidderName{"bidderA": "bidderA", "bidderB": "bidderB"} bidStoredResp1 := json.RawMessage(`[{"bid": [{"id": "bid_id1"],"seat": "bidderA"}]`) bidStoredResp2 := json.RawMessage(`[{"bid": [{"id": "bid_id2"],"seat": "bidderB"}]`) mockStoredResponses := map[string]json.RawMessage{ @@ -604,16 +620,16 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { testCases := []struct { description string - requestJson []byte + request openrtb2.BidRequest expectedErrors []error }{ { description: "Stored bid response with nil data, one bidder one imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderB": { "placementId": 123 }, "prebid": { @@ -621,20 +637,20 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { {"bidder":"bidderB", "id": "3"} ] } - } - } - ]}`), + }`)}, + }, + }, expectedErrors: []error{ errors.New("failed to fetch stored bid response for impId = imp-id1, bidder = bidderB and storedBidResponse id = 3"), }, }, { description: "Stored bid response with nil data, one bidder, two imps, one with correct stored response", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderB": { "placementId": 123 }, "prebid": { @@ -642,12 +658,10 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { {"bidder":"bidderB", "id": "1"} ] } - } - }, - { - "id": "imp-id2", - "ext": { - "appnexus": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ + "bidderB": { "placementId": 123 }, "prebid": { @@ -655,20 +669,20 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { {"bidder":"bidderB", "id": "3"} ] } - } - } - ]}`), + }`)}, + }, + }, expectedErrors: []error{ errors.New("failed to fetch stored bid response for impId = imp-id2, bidder = bidderB and storedBidResponse id = 3"), }, }, { description: "Stored bid response with nil data, one bidder, two imps, both with correct stored response", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { - "appnexus": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ + "bidderB": { "placementId": 123 }, "prebid": { @@ -676,12 +690,10 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { {"bidder":"bidderB", "id": "4"} ] } - } - }, - { - "id": "imp-id2", - "ext": { - "appnexus": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ + "bidderB": { "placementId": 123 }, "prebid": { @@ -689,9 +701,9 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { {"bidder":"bidderB", "id": "3"} ] } - } - } - ]}`), + }`)}, + }, + }, expectedErrors: []error{ errors.New("failed to fetch stored bid response for impId = imp-id1, bidder = bidderB and storedBidResponse id = 4"), errors.New("failed to fetch stored bid response for impId = imp-id2, bidder = bidderB and storedBidResponse id = 3"), @@ -699,10 +711,10 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { }, { description: "Stored auction response with nil data and one imp", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -711,19 +723,19 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { "id": "4" } } - } - } - ]}`), + }`)}, + }, + }, expectedErrors: []error{ errors.New("failed to fetch stored auction response for impId = imp-id1 and storedAuctionResponse id = 4"), }, }, { description: "Stored auction response with nil data, and two imps with nil responses", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -732,11 +744,9 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { "id": "4" } } - } - }, - { - "id": "imp-id2", - "ext": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -745,9 +755,9 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { "id": "3" } } - } - } - ]}`), + }`)}, + }, + }, expectedErrors: []error{ errors.New("failed to fetch stored auction response for impId = imp-id1 and storedAuctionResponse id = 4"), errors.New("failed to fetch stored auction response for impId = imp-id2 and storedAuctionResponse id = 3"), @@ -755,10 +765,10 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { }, { description: "Stored auction response with nil data, two imps, one with nil responses", - requestJson: []byte(`{"imp": [ - { - "id": "imp-id1", - "ext": { + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp-id1", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -767,11 +777,9 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { "id": "2" } } - } - }, - { - "id": "imp-id2", - "ext": { + }`)}, + {ID: "imp-id2", + Ext: json.RawMessage(`{ "appnexus": { "placementId": 123 }, @@ -780,9 +788,9 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { "id": "3" } } - } - } - ]}`), + }`)}, + }, + }, expectedErrors: []error{ errors.New("failed to fetch stored auction response for impId = imp-id2 and storedAuctionResponse id = 3"), }, @@ -790,10 +798,13 @@ func TestProcessStoredResponsesNotFoundResponse(t *testing.T) { } for _, test := range testCases { - _, _, _, errorList := ProcessStoredResponses(nil, test.requestJson, fetcher, bidderMap) - for _, err := range test.expectedErrors { - assert.Contains(t, errorList, err, "incorrect errors returned: %s", test.description) - } + t.Run(test.description, func(t *testing.T) { + rw := openrtb_ext.RequestWrapper{BidRequest: &test.request} + _, _, _, errorList := ProcessStoredResponses(context.TODO(), &rw, fetcher) + for _, err := range test.expectedErrors { + assert.Contains(t, errorList, err) + } + }) } } @@ -847,8 +858,10 @@ func TestFlipMap(t *testing.T) { } for _, test := range testCases { - actualResult := flipMap(test.inImpBidderReplaceImpID) - assert.Equal(t, test.outBidderImpReplaceImpID, actualResult, "Incorrect flipped map for test case %s\n", test.description) + t.Run(test.description, func(t *testing.T) { + actualResult := flipMap(test.inImpBidderReplaceImpID) + assert.Equal(t, test.outBidderImpReplaceImpID, actualResult) + }) } } diff --git a/usersync/chooser.go b/usersync/chooser.go index 578c63717de..31a14a4d431 100644 --- a/usersync/chooser.go +++ b/usersync/chooser.go @@ -1,8 +1,10 @@ package usersync import ( - "github.com/prebid/prebid-server/openrtb_ext" "strings" + + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) // Chooser determines which syncers are eligible for a given request. @@ -13,8 +15,9 @@ type Chooser interface { } // NewChooser returns a new instance of the standard chooser implementation. -func NewChooser(bidderSyncerLookup map[string]Syncer) Chooser { +func NewChooser(bidderSyncerLookup map[string]Syncer, biddersKnown map[string]struct{}, bidderInfo map[string]config.BidderInfo) Chooser { bidders := make([]string, 0, len(bidderSyncerLookup)) + for k := range bidderSyncerLookup { bidders = append(bidders, k) } @@ -24,6 +27,8 @@ func NewChooser(bidderSyncerLookup map[string]Syncer) Chooser { biddersAvailable: bidders, bidderChooser: standardBidderChooser{shuffler: randomShuffler{}}, normalizeValidBidderName: openrtb_ext.NormalizeBidderName, + biddersKnown: biddersKnown, + bidderInfo: bidderInfo, } } @@ -34,6 +39,8 @@ type Request struct { Limit int Privacy Privacy SyncTypeFilter SyncTypeFilter + GPPSID string + Debug bool } // Cooperative specifies the settings for cooperative syncing for a given request, where bidders @@ -73,32 +80,35 @@ const ( // StatusBlockedByUserOptOut specifies a user's cookie explicitly signals an opt-out. StatusBlockedByUserOptOut - // StatusBlockedByGDPR specifies a user's GDPR TCF consent explicitly forbids host cookies - // or specific bidder syncing. - StatusBlockedByGDPR - - // StatusBlockedByCCPA specifies a user's CCPA consent explicitly forbids bidder syncing. - StatusBlockedByCCPA - // StatusAlreadySynced specifies a user's cookie has an existing non-expired sync for a specific bidder. StatusAlreadySynced // StatusUnknownBidder specifies a requested bidder is unknown to Prebid Server. StatusUnknownBidder - // StatusTypeNotSupported specifies a requested sync type is not supported by a specific bidder. - StatusTypeNotSupported + // StatusRejectedByFilter specifies a requested sync type is not supported by a specific bidder. + StatusRejectedByFilter // StatusDuplicate specifies the bidder is a duplicate or shared a syncer key with another bidder choice. StatusDuplicate // StatusBlockedByPrivacy specifies a bidder sync url is not allowed by privacy activities StatusBlockedByPrivacy + + // StatusBlockedByRegulationScope specifies the bidder chose to not sync given GDPR being in scope or because of a GPPSID + StatusBlockedByRegulationScope + + // StatusUnconfiguredBidder refers to a bidder who hasn't been configured to have a syncer key, but is known by Prebid Server + StatusUnconfiguredBidder + + // StatusBlockedByDisabledUsersync refers to a bidder who won't be synced because it's been disabled in its config by the host + StatusBlockedByDisabledUsersync ) // Privacy determines which privacy policies will be enforced for a user sync request. type Privacy interface { GDPRAllowsHostCookie() bool + GDPRInScope() bool GDPRAllowsBidderSync(bidder string) bool CCPAAllowsBidderSync(bidder string) bool ActivityAllowsUserSync(bidder string) bool @@ -110,6 +120,8 @@ type standardChooser struct { biddersAvailable []string bidderChooser bidderChooser normalizeValidBidderName func(name string) (openrtb_ext.BidderName, bool) + biddersKnown map[string]struct{} + bidderInfo map[string]config.BidderInfo } // Choose randomly selects user syncers which are permitted by the user's privacy settings and @@ -120,10 +132,11 @@ func (c standardChooser) Choose(request Request, cookie *Cookie) Result { } if !request.Privacy.GDPRAllowsHostCookie() { - return Result{Status: StatusBlockedByGDPR} + return Result{Status: StatusBlockedByPrivacy} } syncersSeen := make(map[string]struct{}) + biddersSeen := make(map[string]struct{}) limitDisabled := request.Limit <= 0 biddersEvaluated := make([]BidderEvaluation, 0) @@ -131,18 +144,22 @@ func (c standardChooser) Choose(request Request, cookie *Cookie) Result { bidders := c.bidderChooser.choose(request.Bidders, c.biddersAvailable, request.Cooperative) for i := 0; i < len(bidders) && (limitDisabled || len(syncersChosen) < request.Limit); i++ { - syncer, evaluation := c.evaluate(bidders[i], syncersSeen, request.SyncTypeFilter, request.Privacy, cookie) + if _, ok := biddersSeen[bidders[i]]; ok { + continue + } + syncer, evaluation := c.evaluate(bidders[i], syncersSeen, request.SyncTypeFilter, request.Privacy, cookie, request.GPPSID) biddersEvaluated = append(biddersEvaluated, evaluation) if evaluation.Status == StatusOK { syncersChosen = append(syncersChosen, SyncerChoice{Bidder: bidders[i], Syncer: syncer}) } + biddersSeen[bidders[i]] = struct{}{} } return Result{Status: StatusOK, BiddersEvaluated: biddersEvaluated, SyncersChosen: syncersChosen} } -func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{}, syncTypeFilter SyncTypeFilter, privacy Privacy, cookie *Cookie) (Syncer, BidderEvaluation) { +func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{}, syncTypeFilter SyncTypeFilter, privacy Privacy, cookie *Cookie, GPPSID string) (Syncer, BidderEvaluation) { bidderNormalized, exists := c.normalizeValidBidderName(bidder) if !exists { return nil, BidderEvaluation{Status: StatusUnknownBidder, Bidder: bidder} @@ -150,7 +167,11 @@ func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{} syncer, exists := c.bidderSyncerLookup[bidderNormalized.String()] if !exists { - return nil, BidderEvaluation{Status: StatusUnknownBidder, Bidder: bidder} + if _, ok := c.biddersKnown[bidder]; !ok { + return nil, BidderEvaluation{Status: StatusUnknownBidder, Bidder: bidder} + } else { + return nil, BidderEvaluation{Status: StatusUnconfiguredBidder, Bidder: bidder} + } } _, seen := syncersSeen[syncer.Key()] @@ -160,7 +181,7 @@ func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{} syncersSeen[syncer.Key()] = struct{}{} if !syncer.SupportsType(syncTypeFilter.ForBidder(strings.ToLower(bidder))) { - return nil, BidderEvaluation{Status: StatusTypeNotSupported, Bidder: bidder, SyncerKey: syncer.Key()} + return nil, BidderEvaluation{Status: StatusRejectedByFilter, Bidder: bidder, SyncerKey: syncer.Key()} } if cookie.HasLiveSync(syncer.Key()) { @@ -173,11 +194,23 @@ func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{} } if !privacy.GDPRAllowsBidderSync(bidderNormalized.String()) { - return nil, BidderEvaluation{Status: StatusBlockedByGDPR, Bidder: bidder, SyncerKey: syncer.Key()} + return nil, BidderEvaluation{Status: StatusBlockedByPrivacy, Bidder: bidder, SyncerKey: syncer.Key()} + } + + if c.bidderInfo[bidder].Syncer != nil && c.bidderInfo[bidder].Syncer.Enabled != nil && !*c.bidderInfo[bidder].Syncer.Enabled { + return nil, BidderEvaluation{Status: StatusBlockedByDisabledUsersync, Bidder: bidder, SyncerKey: syncer.Key()} + } + + if privacy.GDPRInScope() && c.bidderInfo[bidder].Syncer != nil && c.bidderInfo[bidder].Syncer.SkipWhen != nil && c.bidderInfo[bidder].Syncer.SkipWhen.GDPR { + return nil, BidderEvaluation{Status: StatusBlockedByRegulationScope, Bidder: bidder, SyncerKey: syncer.Key()} } - if !privacy.CCPAAllowsBidderSync(bidderNormalized.String()) { - return nil, BidderEvaluation{Status: StatusBlockedByCCPA, Bidder: bidder, SyncerKey: syncer.Key()} + if c.bidderInfo[bidder].Syncer != nil && c.bidderInfo[bidder].Syncer.SkipWhen != nil { + for _, gppSID := range c.bidderInfo[bidder].Syncer.SkipWhen.GPPSID { + if gppSID == GPPSID { + return nil, BidderEvaluation{Status: StatusBlockedByRegulationScope, Bidder: bidder, SyncerKey: syncer.Key()} + } + } } return syncer, BidderEvaluation{Status: StatusOK, Bidder: bidder, SyncerKey: syncer.Key()} diff --git a/usersync/chooser_test.go b/usersync/chooser_test.go index 6ab5a6b53e3..2674330c301 100644 --- a/usersync/chooser_test.go +++ b/usersync/chooser_test.go @@ -1,19 +1,23 @@ package usersync import ( - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" "testing" "time" - "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/ptrutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/prebid/prebid-server/v3/macros" ) func TestNewChooser(t *testing.T) { testCases := []struct { description string bidderSyncerLookup map[string]Syncer + bidderInfo map[string]config.BidderInfo expectedBiddersAvailable []string }{ { @@ -39,7 +43,7 @@ func TestNewChooser(t *testing.T) { } for _, test := range testCases { - chooser, _ := NewChooser(test.bidderSyncerLookup).(standardChooser) + chooser, _ := NewChooser(test.bidderSyncerLookup, make(map[string]struct{}), test.bidderInfo).(standardChooser) assert.ElementsMatch(t, test.expectedBiddersAvailable, chooser.biddersAvailable, test.description) } } @@ -48,23 +52,33 @@ func TestChooserChoose(t *testing.T) { fakeSyncerA := fakeSyncer{key: "keyA", supportsIFrame: true} fakeSyncerB := fakeSyncer{key: "keyB", supportsIFrame: true} fakeSyncerC := fakeSyncer{key: "keyC", supportsIFrame: false} - bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB, "c": fakeSyncerC, "appnexus": fakeSyncerA} + + duplicateSyncer := fakeSyncer{key: "syncerForDuplicateTest", supportsIFrame: true} + bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB, "c": fakeSyncerC, "appnexus": fakeSyncerA, "d": duplicateSyncer, "e": duplicateSyncer} + biddersKnown := map[string]struct{}{"a": {}, "b": {}, "c": {}} + normalizedBidderNamesLookup := func(name string) (openrtb_ext.BidderName, bool) { return openrtb_ext.BidderName(name), true } + syncerChoiceA := SyncerChoice{Bidder: "a", Syncer: fakeSyncerA} syncerChoiceB := SyncerChoice{Bidder: "b", Syncer: fakeSyncerB} + syncTypeFilter := SyncTypeFilter{ IFrame: NewUniformBidderFilter(BidderFilterModeInclude), - Redirect: NewUniformBidderFilter(BidderFilterModeExclude)} + Redirect: NewUniformBidderFilter(BidderFilterModeExclude), + } cooperativeConfig := Cooperative{Enabled: true} + usersyncDisabled := ptrutil.ToPtr(false) + testCases := []struct { description string givenRequest Request givenChosenBidders []string givenCookie Cookie + givenBidderInfo map[string]config.BidderInfo bidderNamesLookup func(name string) (openrtb_ext.BidderName, bool) expected Result }{ @@ -93,7 +107,7 @@ func TestChooserChoose(t *testing.T) { givenCookie: Cookie{}, bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ - Status: StatusBlockedByGDPR, + Status: StatusBlockedByPrivacy, BiddersEvaluated: nil, SyncersChosen: nil, }, @@ -139,7 +153,22 @@ func TestChooserChoose(t *testing.T) { bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusOK, - BiddersEvaluated: []BidderEvaluation{{Bidder: "c", SyncerKey: "keyC", Status: StatusTypeNotSupported}}, + BiddersEvaluated: []BidderEvaluation{{Bidder: "c", SyncerKey: "keyC", Status: StatusRejectedByFilter}}, + SyncersChosen: []SyncerChoice{}, + }, + }, + { + description: "One Bidder - No Sync - Unknown", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"unknown"}, + givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "unknown", Status: StatusUnknownBidder}}, SyncersChosen: []SyncerChoice{}, }, }, @@ -199,7 +228,7 @@ func TestChooserChoose(t *testing.T) { bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusOK, - BiddersEvaluated: []BidderEvaluation{{Bidder: "c", SyncerKey: "keyC", Status: StatusTypeNotSupported}, {Bidder: "a", SyncerKey: "keyA", Status: StatusOK}}, + BiddersEvaluated: []BidderEvaluation{{Bidder: "c", SyncerKey: "keyC", Status: StatusRejectedByFilter}, {Bidder: "a", SyncerKey: "keyA", Status: StatusOK}}, SyncersChosen: []SyncerChoice{syncerChoiceA}, }, }, @@ -214,10 +243,63 @@ func TestChooserChoose(t *testing.T) { bidderNamesLookup: normalizedBidderNamesLookup, expected: Result{ Status: StatusOK, - BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, {Bidder: "c", SyncerKey: "keyC", Status: StatusTypeNotSupported}}, + BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, {Bidder: "c", SyncerKey: "keyC", Status: StatusRejectedByFilter}}, SyncersChosen: []SyncerChoice{syncerChoiceA}, }, }, + { + description: "Chosen bidders have duplicate syncer keys, the one that comes first should be labled OK", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"d", "e"}, + givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{ + {Bidder: "d", SyncerKey: "syncerForDuplicateTest", Status: StatusOK}, + {Bidder: "e", SyncerKey: "syncerForDuplicateTest", Status: StatusDuplicate}, + }, + SyncersChosen: []SyncerChoice{{Bidder: "d", Syncer: duplicateSyncer}}, + }, + }, + { + description: "Chosen bidders have duplicate syncer keys, the one that comes first should be labled OK (reverse)", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"e", "d"}, + givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{ + {Bidder: "e", SyncerKey: "syncerForDuplicateTest", Status: StatusOK}, + {Bidder: "d", SyncerKey: "syncerForDuplicateTest", Status: StatusDuplicate}, + }, + SyncersChosen: []SyncerChoice{{Bidder: "e", Syncer: duplicateSyncer}}, + }, + }, + { + description: "Same bidder name, no duplicate warning", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"a", "a"}, + givenCookie: Cookie{}, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{ + {Bidder: "a", SyncerKey: fakeSyncerA.key, Status: StatusOK}, + }, + SyncersChosen: []SyncerChoice{{Bidder: "a", Syncer: fakeSyncerA}}, + }, + }, { description: "Unknown Bidder", givenRequest: Request{ @@ -265,6 +347,77 @@ func TestChooserChoose(t *testing.T) { SyncersChosen: []SyncerChoice{{Bidder: "AppNexus", Syncer: fakeSyncerA}}, }, }, + { + description: "Disabled Usersync", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"a"}, + givenCookie: Cookie{}, + givenBidderInfo: map[string]config.BidderInfo{ + "a": { + Syncer: &config.Syncer{ + Enabled: usersyncDisabled, + }, + }, + }, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByDisabledUsersync}}, + SyncersChosen: []SyncerChoice{}, + }, + }, + { + description: "Regulation Scope GDPR", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true, gdprInScope: true}, + Limit: 0, + }, + givenChosenBidders: []string{"a"}, + givenCookie: Cookie{}, + givenBidderInfo: map[string]config.BidderInfo{ + "a": { + Syncer: &config.Syncer{ + SkipWhen: &config.SkipWhen{ + GDPR: true, + }, + }, + }, + }, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByRegulationScope}}, + SyncersChosen: []SyncerChoice{}, + }, + }, + { + description: "Regulation Scope GPP", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + GPPSID: "2", + }, + givenChosenBidders: []string{"a"}, + givenCookie: Cookie{}, + givenBidderInfo: map[string]config.BidderInfo{ + "a": { + Syncer: &config.Syncer{ + SkipWhen: &config.SkipWhen{ + GPPSID: []string{"2", "3"}, + }, + }, + }, + }, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByRegulationScope}}, + SyncersChosen: []SyncerChoice{}, + }, + }, } bidders := []string{"anyRequested"} @@ -280,11 +433,17 @@ func TestChooserChoose(t *testing.T) { On("choose", test.givenRequest.Bidders, biddersAvailable, cooperativeConfig). Return(test.givenChosenBidders) + if test.givenBidderInfo == nil { + test.givenBidderInfo = map[string]config.BidderInfo{} + } + chooser := standardChooser{ bidderSyncerLookup: bidderSyncerLookup, biddersAvailable: biddersAvailable, bidderChooser: mockBidderChooser, normalizeValidBidderName: test.bidderNamesLookup, + biddersKnown: biddersKnown, + bidderInfo: test.givenBidderInfo, } result := chooser.Choose(test.givenRequest, &test.givenCookie) @@ -295,10 +454,14 @@ func TestChooserChoose(t *testing.T) { func TestChooserEvaluate(t *testing.T) { fakeSyncerA := fakeSyncer{key: "keyA", supportsIFrame: true} fakeSyncerB := fakeSyncer{key: "keyB", supportsIFrame: false} - bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB, "appnexus": fakeSyncerA, "suntContent": fakeSyncerA} + + biddersKnown := map[string]struct{}{"a": {}, "b": {}, "unconfigured": {}} + bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB, "appnexus": fakeSyncerA, "seedingAlliance": fakeSyncerA} + syncTypeFilter := SyncTypeFilter{ IFrame: NewUniformBidderFilter(BidderFilterModeInclude), - Redirect: NewUniformBidderFilter(BidderFilterModeExclude)} + Redirect: NewUniformBidderFilter(BidderFilterModeExclude), + } normalizedBidderNamesLookup := func(name string) (openrtb_ext.BidderName, bool) { return openrtb_ext.BidderName(name), true } @@ -306,6 +469,8 @@ func TestChooserEvaluate(t *testing.T) { cookieAlreadyHasSyncForA := Cookie{uids: map[string]UIDEntry{"keyA": {Expires: time.Now().Add(time.Duration(24) * time.Hour)}}} cookieAlreadyHasSyncForB := Cookie{uids: map[string]UIDEntry{"keyB": {Expires: time.Now().Add(time.Duration(24) * time.Hour)}}} + usersyncDisabled := ptrutil.ToPtr(false) + testCases := []struct { description string givenBidder string @@ -313,6 +478,8 @@ func TestChooserEvaluate(t *testing.T) { givenSyncersSeen map[string]struct{} givenPrivacy fakePrivacy givenCookie Cookie + givenGPPSID string + givenBidderInfo map[string]config.BidderInfo givenSyncTypeFilter SyncTypeFilter normalizedBidderNamesLookup func(name string) (openrtb_ext.BidderName, bool) expectedSyncer Syncer @@ -364,7 +531,7 @@ func TestChooserEvaluate(t *testing.T) { givenSyncTypeFilter: syncTypeFilter, normalizedBidderNamesLookup: normalizedBidderNamesLookup, expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "b", SyncerKey: "keyB", Status: StatusTypeNotSupported}, + expectedEvaluation: BidderEvaluation{Bidder: "b", SyncerKey: "keyB", Status: StatusRejectedByFilter}, }, { description: "Already Synced", @@ -400,19 +567,7 @@ func TestChooserEvaluate(t *testing.T) { givenSyncTypeFilter: syncTypeFilter, normalizedBidderNamesLookup: normalizedBidderNamesLookup, expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByGDPR}, - }, - { - description: "Blocked By CCPA", - givenBidder: "a", - normalisedBidderName: "a", - givenSyncersSeen: map[string]struct{}{}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: false, activityAllowUserSync: true}, - givenCookie: cookieNeedsSync, - givenSyncTypeFilter: syncTypeFilter, - normalizedBidderNamesLookup: normalizedBidderNamesLookup, - expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByCCPA}, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByPrivacy}, }, { description: "Blocked By activity control", @@ -440,17 +595,18 @@ func TestChooserEvaluate(t *testing.T) { }, { description: "Case insensitivity check for sync type filter", - givenBidder: "SuntContent", - normalisedBidderName: "suntContent", + givenBidder: "SeedingAlliance", + normalisedBidderName: "seedingAlliance", givenSyncersSeen: map[string]struct{}{}, givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, givenCookie: cookieNeedsSync, givenSyncTypeFilter: SyncTypeFilter{ - IFrame: NewSpecificBidderFilter([]string{"SuntContent"}, BidderFilterModeInclude), - Redirect: NewSpecificBidderFilter([]string{"SuntContent"}, BidderFilterModeExclude)}, + IFrame: NewSpecificBidderFilter([]string{"SeedingAlliance"}, BidderFilterModeInclude), + Redirect: NewSpecificBidderFilter([]string{"SeedingAlliance"}, BidderFilterModeExclude), + }, normalizedBidderNamesLookup: openrtb_ext.NormalizeBidderName, expectedSyncer: fakeSyncerA, - expectedEvaluation: BidderEvaluation{Bidder: "SuntContent", SyncerKey: "keyA", Status: StatusOK}, + expectedEvaluation: BidderEvaluation{Bidder: "SeedingAlliance", SyncerKey: "keyA", Status: StatusOK}, }, { description: "Case Insensitivity Check For Blocked By GDPR", @@ -462,27 +618,87 @@ func TestChooserEvaluate(t *testing.T) { givenSyncTypeFilter: syncTypeFilter, normalizedBidderNamesLookup: openrtb_ext.NormalizeBidderName, expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "AppNexus", SyncerKey: "keyA", Status: StatusBlockedByGDPR}, + expectedEvaluation: BidderEvaluation{Bidder: "AppNexus", SyncerKey: "keyA", Status: StatusBlockedByPrivacy}, }, { - description: "Case Insensitivity Check For Blocked By CCPA", - givenBidder: "AppNexus", - normalisedBidderName: "appnexus", + description: "Unconfigured Bidder", + givenBidder: "unconfigured", + normalizedBidderNamesLookup: normalizedBidderNamesLookup, givenSyncersSeen: map[string]struct{}{}, - givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: false, activityAllowUserSync: true}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, givenCookie: cookieNeedsSync, + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "unconfigured", Status: StatusUnconfiguredBidder}, + }, + { + description: "Disabled Usersync", + givenBidder: "a", + normalisedBidderName: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenBidderInfo: map[string]config.BidderInfo{ + "a": { + Syncer: &config.Syncer{ + Enabled: usersyncDisabled, + }, + }, + }, givenSyncTypeFilter: syncTypeFilter, - normalizedBidderNamesLookup: openrtb_ext.NormalizeBidderName, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, expectedSyncer: nil, - expectedEvaluation: BidderEvaluation{Bidder: "AppNexus", SyncerKey: "keyA", Status: StatusBlockedByCCPA}, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByDisabledUsersync}, + }, + { + description: "Blocked By Regulation Scope - GDPR", + givenBidder: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true, gdprInScope: true}, + givenCookie: cookieNeedsSync, + givenBidderInfo: map[string]config.BidderInfo{ + "a": { + Syncer: &config.Syncer{ + SkipWhen: &config.SkipWhen{ + GDPR: true, + }, + }, + }, + }, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + normalisedBidderName: "a", + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByRegulationScope}, + }, + { + description: "Blocked By Regulation Scope - GPP", + givenBidder: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenBidderInfo: map[string]config.BidderInfo{ + "a": { + Syncer: &config.Syncer{ + SkipWhen: &config.SkipWhen{ + GPPSID: []string{"2", "3"}, + }, + }, + }, + }, + givenGPPSID: "2", + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + normalisedBidderName: "a", + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByRegulationScope}, }, } for _, test := range testCases { t.Run(test.description, func(t *testing.T) { - chooser, _ := NewChooser(bidderSyncerLookup).(standardChooser) + chooser, _ := NewChooser(bidderSyncerLookup, biddersKnown, test.givenBidderInfo).(standardChooser) chooser.normalizeValidBidderName = test.normalizedBidderNamesLookup - sync, evaluation := chooser.evaluate(test.givenBidder, test.givenSyncersSeen, test.givenSyncTypeFilter, &test.givenPrivacy, &test.givenCookie) + sync, evaluation := chooser.evaluate(test.givenBidder, test.givenSyncersSeen, test.givenSyncTypeFilter, &test.givenPrivacy, &test.givenCookie, test.givenGPPSID) assert.Equal(t, test.normalisedBidderName, test.givenPrivacy.inputBidderName) assert.Equal(t, test.expectedSyncer, sync, test.description+":syncer") @@ -504,13 +720,14 @@ type fakeSyncer struct { key string supportsIFrame bool supportsRedirect bool + formatOverride string } func (s fakeSyncer) Key() string { return s.key } -func (s fakeSyncer) DefaultSyncType() SyncType { +func (s fakeSyncer) DefaultResponseFormat() SyncType { return SyncTypeIFrame } @@ -535,6 +752,7 @@ type fakePrivacy struct { gdprAllowsBidderSync bool ccpaAllowsBidderSync bool activityAllowUserSync bool + gdprInScope bool inputBidderName string } @@ -555,3 +773,7 @@ func (p *fakePrivacy) CCPAAllowsBidderSync(bidder string) bool { func (p *fakePrivacy) ActivityAllowsUserSync(bidder string) bool { return p.activityAllowUserSync } + +func (p *fakePrivacy) GDPRInScope() bool { + return p.gdprInScope +} diff --git a/usersync/cookie.go b/usersync/cookie.go index 88524018c49..dce5b02095c 100644 --- a/usersync/cookie.go +++ b/usersync/cookie.go @@ -5,9 +5,9 @@ import ( "net/http" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) const uidCookieName = "uids" @@ -225,7 +225,7 @@ type cookieJson struct { OptOut bool `json:"optout,omitempty"` } -func (cookie *Cookie) MarshalJSON() ([]byte, error) { +func (cookie *Cookie) MarshalJSON() ([]byte, error) { // nosemgrep: marshal-json-pointer-receiver return jsonutil.Marshal(cookieJson{ UIDs: cookie.uids, OptOut: cookie.optOut, diff --git a/usersync/cookie_test.go b/usersync/cookie_test.go index 340069767b1..79193d92a65 100644 --- a/usersync/cookie_test.go +++ b/usersync/cookie_test.go @@ -7,8 +7,8 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -646,13 +646,13 @@ func TestReadCookieOptOut(t *testing.T) { optOutCookieValue := "optOutCookieValue" decoder := Base64Decoder{} - cookie := *(&Cookie{ + cookie := Cookie{ uids: map[string]UIDEntry{ "foo": newTempId("fooID", 1), "bar": newTempId("barID", 2), }, optOut: false, - }) + } existingCookie, _ := ToHTTPCookie(&cookie) diff --git a/usersync/decoder.go b/usersync/decoder.go index c803fbe2a52..d77c8d426bf 100644 --- a/usersync/decoder.go +++ b/usersync/decoder.go @@ -3,7 +3,7 @@ package usersync import ( "encoding/base64" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type Decoder interface { diff --git a/usersync/encoder.go b/usersync/encoder.go index 74472f23307..6a7db1fe519 100644 --- a/usersync/encoder.go +++ b/usersync/encoder.go @@ -3,7 +3,7 @@ package usersync import ( "encoding/base64" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) type Encoder interface { diff --git a/usersync/syncer.go b/usersync/syncer.go index e561614f4a2..b2fec616102 100644 --- a/usersync/syncer.go +++ b/usersync/syncer.go @@ -9,8 +9,8 @@ import ( "text/template" validator "github.com/asaskevich/govalidator" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/macros" ) var ( @@ -26,8 +26,8 @@ type Syncer interface { // necessarily, a one-to-one mapping with a bidder. Key() string - // DefaultSyncType is the default SyncType for this syncer. - DefaultSyncType() SyncType + // DefaultResponseFormat is the default SyncType for this syncer. + DefaultResponseFormat() SyncType // SupportsType returns true if the syncer supports at least one of the specified sync types. SupportsType(syncTypes []SyncType) bool @@ -50,13 +50,9 @@ type standardSyncer struct { iframe *template.Template redirect *template.Template supportCORS bool + formatOverride string } -const ( - setuidSyncTypeIFrame = "b" // b = blank HTML response - setuidSyncTypeRedirect = "i" // i = image response -) - // NewSyncer creates a new Syncer from the provided configuration, or return an error if macro substition // fails or an endpoint url is invalid. func NewSyncer(hostConfig config.UserSync, syncerConfig config.Syncer, bidder string) (Syncer, error) { @@ -72,11 +68,12 @@ func NewSyncer(hostConfig config.UserSync, syncerConfig config.Syncer, bidder st key: syncerConfig.Key, defaultSyncType: resolveDefaultSyncType(syncerConfig), supportCORS: syncerConfig.SupportCORS != nil && *syncerConfig.SupportCORS, + formatOverride: syncerConfig.FormatOverride, } if syncerConfig.IFrame != nil { var err error - syncer.iframe, err = buildTemplate(bidder, setuidSyncTypeIFrame, hostConfig, syncerConfig.ExternalURL, *syncerConfig.IFrame) + syncer.iframe, err = buildTemplate(bidder, config.SyncResponseFormatIFrame, hostConfig, syncerConfig.ExternalURL, *syncerConfig.IFrame, syncerConfig.FormatOverride) if err != nil { return nil, fmt.Errorf("iframe %v", err) } @@ -87,7 +84,7 @@ func NewSyncer(hostConfig config.UserSync, syncerConfig config.Syncer, bidder st if syncerConfig.Redirect != nil { var err error - syncer.redirect, err = buildTemplate(bidder, setuidSyncTypeRedirect, hostConfig, syncerConfig.ExternalURL, *syncerConfig.Redirect) + syncer.redirect, err = buildTemplate(bidder, config.SyncResponseFormatRedirect, hostConfig, syncerConfig.ExternalURL, *syncerConfig.Redirect, syncerConfig.FormatOverride) if err != nil { return nil, fmt.Errorf("redirect %v", err) } @@ -117,12 +114,16 @@ var ( macroRegex = regexp.MustCompile(`{{\s*\..*?\s*}}`) ) -func buildTemplate(bidderName, syncTypeValue string, hostConfig config.UserSync, syncerExternalURL string, syncerEndpoint config.SyncerEndpoint) (*template.Template, error) { +func buildTemplate(bidderName, syncTypeValue string, hostConfig config.UserSync, syncerExternalURL string, syncerEndpoint config.SyncerEndpoint, formatOverride string) (*template.Template, error) { redirectTemplate := syncerEndpoint.RedirectURL if redirectTemplate == "" { redirectTemplate = hostConfig.RedirectURL } + if formatOverride != "" { + syncTypeValue = formatOverride + } + externalURL := chooseExternalURL(syncerEndpoint.ExternalURL, syncerExternalURL, hostConfig.ExternalURL) redirectURL := macroRegexSyncerKey.ReplaceAllLiteralString(redirectTemplate, bidderName) @@ -189,8 +190,15 @@ func (s standardSyncer) Key() string { return s.key } -func (s standardSyncer) DefaultSyncType() SyncType { - return s.defaultSyncType +func (s standardSyncer) DefaultResponseFormat() SyncType { + switch s.formatOverride { + case config.SyncResponseFormatIFrame: + return SyncTypeIFrame + case config.SyncResponseFormatRedirect: + return SyncTypeRedirect + default: + return s.defaultSyncType + } } func (s standardSyncer) SupportsType(syncTypes []SyncType) bool { diff --git a/usersync/syncer_test.go b/usersync/syncer_test.go index 72167addae5..020fb26df66 100644 --- a/usersync/syncer_test.go +++ b/usersync/syncer_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/macros" "github.com/stretchr/testify/assert" ) @@ -27,6 +27,7 @@ func TestNewSyncer(t *testing.T) { givenIFrameConfig *config.SyncerEndpoint givenRedirectConfig *config.SyncerEndpoint givenExternalURL string + givenForceType string expectedError string expectedDefault SyncType expectedIFrame string @@ -322,7 +323,7 @@ func TestBuildTemplate(t *testing.T) { } for _, test := range testCases { - result, err := buildTemplate(key, syncTypeValue, hostConfig, test.givenSyncerExternalURL, test.givenSyncerEndpoint) + result, err := buildTemplate(key, syncTypeValue, hostConfig, test.givenSyncerExternalURL, test.givenSyncerEndpoint, "") if test.expectedError == "" { assert.NoError(t, err, test.description+":err") @@ -480,7 +481,36 @@ func TestSyncerKey(t *testing.T) { func TestSyncerDefaultSyncType(t *testing.T) { syncer := standardSyncer{defaultSyncType: SyncTypeRedirect} - assert.Equal(t, SyncTypeRedirect, syncer.DefaultSyncType()) + assert.Equal(t, SyncTypeRedirect, syncer.DefaultResponseFormat()) +} + +func TestSyncerDefaultResponseFormat(t *testing.T) { + testCases := []struct { + description string + givenSyncer standardSyncer + expectedSyncType SyncType + }{ + { + description: "IFrame", + givenSyncer: standardSyncer{formatOverride: config.SyncResponseFormatIFrame}, + expectedSyncType: SyncTypeIFrame, + }, + { + description: "Default with Redirect Override", + givenSyncer: standardSyncer{defaultSyncType: SyncTypeIFrame, formatOverride: config.SyncResponseFormatRedirect}, + expectedSyncType: SyncTypeRedirect, + }, + { + description: "Default with no override", + givenSyncer: standardSyncer{defaultSyncType: SyncTypeRedirect}, + expectedSyncType: SyncTypeRedirect, + }, + } + + for _, test := range testCases { + syncType := test.givenSyncer.DefaultResponseFormat() + assert.Equal(t, test.expectedSyncType, syncType, test.description) + } } func TestSyncerSupportsType(t *testing.T) { diff --git a/usersync/syncersbuilder.go b/usersync/syncersbuilder.go index 9a52a740f31..b79cdf48c7f 100644 --- a/usersync/syncersbuilder.go +++ b/usersync/syncersbuilder.go @@ -5,7 +5,7 @@ import ( "sort" "strings" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/v3/config" ) type namedSyncerConfig struct { @@ -84,13 +84,9 @@ func shouldCreateSyncer(cfg config.BidderInfo) bool { return false } - if cfg.Syncer == nil { - return false - } - // a syncer may provide just a Supports field to provide hints to the host. we should only try to create a syncer // if there is at least one non-Supports value populated. - return cfg.Syncer.Key != "" || cfg.Syncer.IFrame != nil || cfg.Syncer.Redirect != nil || cfg.Syncer.SupportCORS != nil + return cfg.Syncer.Defined() } func chooseSyncerConfig(biddersSyncerConfig []namedSyncerConfig) (namedSyncerConfig, error) { diff --git a/usersync/syncersbuilder_test.go b/usersync/syncersbuilder_test.go index 15c53dba2a4..041c83a7b58 100644 --- a/usersync/syncersbuilder_test.go +++ b/usersync/syncersbuilder_test.go @@ -4,8 +4,8 @@ import ( "errors" "testing" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/macros" "github.com/stretchr/testify/assert" ) diff --git a/util/httputil/httputil.go b/util/httputil/httputil.go index 28334a54b87..dd09cdadf42 100644 --- a/util/httputil/httputil.go +++ b/util/httputil/httputil.go @@ -5,7 +5,7 @@ import ( "net/http" "strings" - "github.com/prebid/prebid-server/util/iputil" + "github.com/prebid/prebid-server/v3/util/iputil" ) var ( diff --git a/util/httputil/httputil_test.go b/util/httputil/httputil_test.go index 5da3b0ab735..2b2f52e5c57 100644 --- a/util/httputil/httputil_test.go +++ b/util/httputil/httputil_test.go @@ -5,7 +5,7 @@ import ( "net/http" "testing" - "github.com/prebid/prebid-server/util/iputil" + "github.com/prebid/prebid-server/v3/util/iputil" "github.com/stretchr/testify/assert" ) diff --git a/util/jsonutil/jsonutil.go b/util/jsonutil/jsonutil.go index a8981477dc6..1e460196fc4 100644 --- a/util/jsonutil/jsonutil.go +++ b/util/jsonutil/jsonutil.go @@ -5,9 +5,11 @@ import ( "encoding/json" "io" "strings" + "unsafe" jsoniter "github.com/json-iterator/go" - "github.com/prebid/prebid-server/errortypes" + "github.com/modern-go/reflect2" + "github.com/prebid/prebid-server/v3/errortypes" ) var comma = byte(',') @@ -211,3 +213,38 @@ func tryExtractErrorMessage(err error) string { func isLikelyDetailedErrorMessage(msg string) bool { return !strings.HasPrefix(msg, "request.") } + +// RawMessageExtension will call json.Compact() on every json.RawMessage field when getting marshalled. +type RawMessageExtension struct { + jsoniter.DummyExtension +} + +// CreateEncoder substitutes the default jsoniter encoder of the json.RawMessage type with ours, that +// calls json.Compact() before writting to the stream +func (e *RawMessageExtension) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder { + if typ == jsonRawMessageType { + return &rawMessageCodec{} + } + return nil +} + +var jsonRawMessageType = reflect2.TypeOfPtr((*json.RawMessage)(nil)).Elem() + +// rawMessageCodec implements jsoniter.ValEncoder interface so we can override the default json.RawMessage Encode() +// function with our implementation +type rawMessageCodec struct{} + +func (codec *rawMessageCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) { + if ptr != nil { + jsonRawMsg := *(*[]byte)(ptr) + + dst := bytes.NewBuffer(make([]byte, 0, len(jsonRawMsg))) + if err := json.Compact(dst, jsonRawMsg); err == nil { + stream.Write(dst.Bytes()) + } + } +} + +func (codec *rawMessageCodec) IsEmpty(ptr unsafe.Pointer) bool { + return ptr == nil || len(*((*json.RawMessage)(ptr))) == 0 +} diff --git a/util/jsonutil/jsonutil_test.go b/util/jsonutil/jsonutil_test.go index 09fb6727309..96632d62548 100644 --- a/util/jsonutil/jsonutil_test.go +++ b/util/jsonutil/jsonutil_test.go @@ -1,10 +1,15 @@ package jsonutil import ( + "bytes" + "encoding/json" "errors" "strings" "testing" + "unsafe" + jsoniter "github.com/json-iterator/go" + "github.com/modern-go/reflect2" "github.com/stretchr/testify/assert" ) @@ -240,3 +245,80 @@ func TestTryExtractErrorMessage(t *testing.T) { }) } } + +func TestCreateEncoder(t *testing.T) { + testCases := []struct { + desc string + inType reflect2.Type + expectedValEncoder jsoniter.ValEncoder + }{ + { + desc: "With_extension", + inType: reflect2.TypeOfPtr((*jsoniter.Any)(nil)).Elem(), + expectedValEncoder: nil, + }, + { + desc: "No_extension", + inType: reflect2.TypeOfPtr(&json.RawMessage{}).Elem(), + expectedValEncoder: &rawMessageCodec{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + extension := &RawMessageExtension{} + encoder := extension.CreateEncoder(tc.inType) + assert.IsType(t, encoder, tc.expectedValEncoder) + }) + } +} + +func TestEncode(t *testing.T) { + jsonBlob := json.RawMessage(`{ + "properties": { + "string": "Blanks spaces in between words to not be removed if compacted", + "integer": 5, + "string_array": [ + "string array elem one", + "string array elem two" + ] + } +}`) + + t.Run( + "Nil_pointer", + func(t *testing.T) { + // set test + encoder := &rawMessageCodec{} + output := bytes.NewBuffer([]byte{}) + stream := jsoniter.NewStream(jsonConfigValidationOn, output, len(jsonBlob)) + + // run + encoder.Encode(nil, stream) + + // assertions + assert.Equal(t, "", output.String()) + assert.Equal(t, true, encoder.IsEmpty(nil)) + }, + ) + t.Run( + "json.RawMessage_compact_JSON", + func(t *testing.T) { + // set test + encoder := &rawMessageCodec{} + output := bytes.NewBuffer([]byte{}) + stream := jsoniter.NewStream(jsonConfigValidationOn, output, len(jsonBlob)) + + // run + encoder.Encode(unsafe.Pointer(&jsonBlob), stream) + + // assertions + assert.Equal( + t, + `{"properties":{"string":"Blanks spaces in between words to not be removed if compacted","integer":5,"string_array":["string array elem one","string array elem two"]}}`, + output.String(), + ) + assert.Equal(t, false, encoder.IsEmpty(unsafe.Pointer(&jsonBlob))) + }, + ) +} diff --git a/util/jsonutil/merge.go b/util/jsonutil/merge.go new file mode 100644 index 00000000000..2b7edc6ee25 --- /dev/null +++ b/util/jsonutil/merge.go @@ -0,0 +1,176 @@ +package jsonutil + +import ( + "encoding/json" + "errors" + "reflect" + "unsafe" + + jsoniter "github.com/json-iterator/go" + "github.com/modern-go/reflect2" + jsonpatch "gopkg.in/evanphx/json-patch.v4" + + "github.com/prebid/prebid-server/v3/errortypes" +) + +// jsonConfigMergeClone uses the same configuration as the `ConfigCompatibleWithStandardLibrary` profile +// with extensions added to support the merge clone behavior. +var jsonConfigMergeClone = jsoniter.Config{ + EscapeHTML: true, + SortMapKeys: true, + ValidateJsonRawMessage: true, +}.Froze() + +func init() { + jsonConfigMergeClone.RegisterExtension(&mergeCloneExtension{}) +} + +// MergeClone unmarshals json data on top of an existing object and clones pointers of +// the existing object before setting new values. Slices and maps are also cloned. +// Fields of type json.RawMessage are merged rather than replaced. +func MergeClone(v any, data json.RawMessage) error { + err := jsonConfigMergeClone.Unmarshal(data, v) + if err == nil { + return nil + } + return &errortypes.FailedToUnmarshal{ + Message: tryExtractErrorMessage(err), + } +} + +type mergeCloneExtension struct { + jsoniter.DummyExtension +} + +func (e *mergeCloneExtension) CreateDecoder(typ reflect2.Type) jsoniter.ValDecoder { + if typ == jsonRawMessageType { + return &extMergeDecoder{sliceType: typ.(*reflect2.UnsafeSliceType)} + } + return nil +} + +func (e *mergeCloneExtension) DecorateDecoder(typ reflect2.Type, decoder jsoniter.ValDecoder) jsoniter.ValDecoder { + if typ.Kind() == reflect.Ptr { + ptrType := typ.(*reflect2.UnsafePtrType) + return &ptrCloneDecoder{valueDecoder: decoder, elemType: ptrType.Elem()} + } + + // don't use json.RawMessage on fields handled by extMergeDecoder + if typ.Kind() == reflect.Slice && typ != jsonRawMessageType { + return &sliceCloneDecoder{valueDecoder: decoder, sliceType: typ.(*reflect2.UnsafeSliceType)} + } + + if typ.Kind() == reflect.Map { + return &mapCloneDecoder{valueDecoder: decoder, mapType: typ.(*reflect2.UnsafeMapType)} + } + + return decoder +} + +type ptrCloneDecoder struct { + elemType reflect2.Type + valueDecoder jsoniter.ValDecoder +} + +func (d *ptrCloneDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { + // don't clone if field is being set to nil. checking for nil "consumes" the null + // token, so must be handled in this decoder. + if iter.ReadNil() { + *((*unsafe.Pointer)(ptr)) = nil + return + } + + // clone if there is an existing object. creation of new objects is handled by the + // original decoder. + if *((*unsafe.Pointer)(ptr)) != nil { + obj := d.elemType.UnsafeNew() + d.elemType.UnsafeSet(obj, *((*unsafe.Pointer)(ptr))) + *((*unsafe.Pointer)(ptr)) = obj + } + + d.valueDecoder.Decode(ptr, iter) +} + +type sliceCloneDecoder struct { + sliceType *reflect2.UnsafeSliceType + valueDecoder jsoniter.ValDecoder +} + +func (d *sliceCloneDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { + // clear the field. a new slice will be created by the original decoder if needed. + d.sliceType.UnsafeSetNil(ptr) + + // checking for nil "consumes" the null token, so must be handled in this decoder. + if iter.ReadNil() { + return + } + + d.valueDecoder.Decode(ptr, iter) +} + +type mapCloneDecoder struct { + mapType *reflect2.UnsafeMapType + valueDecoder jsoniter.ValDecoder +} + +func (d *mapCloneDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { + // don't clone if field is being set to nil. checking for nil "consumes" the null + // token, so must be handled in this decoder. + if iter.ReadNil() { + *(*unsafe.Pointer)(ptr) = nil + d.mapType.UnsafeSet(ptr, d.mapType.UnsafeNew()) + return + } + + // clone if there is an existing object. creation of new objects is handled by the + // original decoder. + if !d.mapType.UnsafeIsNil(ptr) { + clone := d.mapType.UnsafeMakeMap(0) + mapIter := d.mapType.UnsafeIterate(ptr) + for mapIter.HasNext() { + key, elem := mapIter.UnsafeNext() + d.mapType.UnsafeSetIndex(clone, key, elem) + } + d.mapType.UnsafeSet(ptr, clone) + } + + d.valueDecoder.Decode(ptr, iter) +} + +type extMergeDecoder struct { + sliceType *reflect2.UnsafeSliceType +} + +func (d *extMergeDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { + // incoming nil value, keep existing + if iter.ReadNil() { + return + } + + existing := *((*json.RawMessage)(ptr)) + incoming := iter.SkipAndReturnBytes() + + // check for read errors to avoid calling jsonpatch.MergePatch on bad data. + if iter.Error != nil { + return + } + + // existing empty value, use incoming + if len(existing) == 0 { + *((*json.RawMessage)(ptr)) = incoming + return + } + + // non-empty incoming and existing values, merge + merged, err := jsonpatch.MergePatch(existing, incoming) + if err != nil { + if errors.Is(err, jsonpatch.ErrBadJSONDoc) { + iter.ReportError("merge", "invalid json on existing object") + } else { + iter.ReportError("merge", err.Error()) + } + return + } + + *((*json.RawMessage)(ptr)) = merged +} diff --git a/util/jsonutil/merge_test.go b/util/jsonutil/merge_test.go new file mode 100644 index 00000000000..b4e58197be1 --- /dev/null +++ b/util/jsonutil/merge_test.go @@ -0,0 +1,470 @@ +package jsonutil + +import ( + "encoding/json" + "slices" + "testing" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMergeClonePtr(t *testing.T) { + t.Run("root", func(t *testing.T) { + var ( + banner = &openrtb2.Banner{ID: "1"} + imp = &openrtb2.Imp{Banner: banner} + impOriginal = imp + ) + + // root objects are not cloned + err := MergeClone(imp, []byte(`{"banner":{"id":"4"}}`)) + require.NoError(t, err) + + assert.Same(t, impOriginal, imp, "imp-ref") + assert.NotSame(t, imp.Banner, banner, "banner-ref") + }) + + t.Run("embedded-nil", func(t *testing.T) { + var ( + banner = &openrtb2.Banner{ID: "1"} + video = &openrtb2.Video{PodID: "a"} + imp = &openrtb2.Imp{Banner: banner, Video: video} + ) + + err := MergeClone(imp, []byte(`{"banner":null}`)) + require.NoError(t, err) + + assert.NotSame(t, banner, imp.Banner, "banner-ref") + assert.Same(t, video, imp.Video, "video") + assert.Nil(t, imp.Banner, "banner-nil") + }) + + t.Run("embedded-struct", func(t *testing.T) { + var ( + banner = &openrtb2.Banner{ID: "1"} + video = &openrtb2.Video{PodID: "a"} + imp = &openrtb2.Imp{Banner: banner, Video: video} + ) + + err := MergeClone(imp, []byte(`{"banner":{"id":"2"}}`)) + require.NoError(t, err) + + assert.NotSame(t, banner, imp.Banner, "banner-ref") + assert.Same(t, video, imp.Video, "video-ref") + assert.Equal(t, "1", banner.ID, "id-original") + assert.Equal(t, "2", imp.Banner.ID, "id-clone") + }) + + t.Run("embedded-int", func(t *testing.T) { + var ( + clickbrowser = int8(1) + imp = &openrtb2.Imp{ClickBrowser: &clickbrowser} + ) + + err := MergeClone(imp, []byte(`{"clickbrowser":2}`)) + require.NoError(t, err) + + require.NotNil(t, imp.ClickBrowser, "clickbrowser-nil") + assert.NotSame(t, clickbrowser, imp.ClickBrowser, "clickbrowser-ref") + assert.Equal(t, int8(2), *imp.ClickBrowser, "clickbrowser-val") + }) + + t.Run("invalid-null", func(t *testing.T) { + var ( + banner = &openrtb2.Banner{ID: "1"} + imp = &openrtb2.Imp{Banner: banner} + ) + + err := MergeClone(imp, []byte(`{"banner":nul}`)) + + // json-iter will produce an error since "nul" is not a valid json value. the + // parsing code will see the "n" and then expect "ull" to follow. the strange + // "expect ull" error being asserted is generated by json-iter. + require.EqualError(t, err, "cannot unmarshal openrtb2.Imp.Banner: expect ull") + }) + + t.Run("invalid-malformed", func(t *testing.T) { + var ( + banner = &openrtb2.Banner{ID: "1"} + imp = &openrtb2.Imp{Banner: banner} + ) + + err := MergeClone(imp, []byte(`{"banner":malformed}`)) + require.EqualError(t, err, "cannot unmarshal openrtb2.Imp.Banner: expect { or n, but found m") + }) +} + +func TestMergeCloneSlice(t *testing.T) { + t.Run("null", func(t *testing.T) { + var ( + iframeBuster = []string{"a", "b"} + imp = &openrtb2.Imp{IframeBuster: iframeBuster} + ) + + err := MergeClone(imp, []byte(`{"iframeBuster":null}`)) + require.NoError(t, err) + + assert.Equal(t, []string{"a", "b"}, iframeBuster, "iframeBuster-val") + assert.Nil(t, imp.IframeBuster, "iframeBuster-nil") + }) + + t.Run("one", func(t *testing.T) { + var ( + iframeBuster = []string{"a"} + imp = &openrtb2.Imp{IframeBuster: iframeBuster} + ) + + err := MergeClone(imp, []byte(`{"iframeBuster":["b"]}`)) + require.NoError(t, err) + + assert.NotSame(t, iframeBuster, imp.IframeBuster, "ref") + assert.Equal(t, []string{"a"}, iframeBuster, "original-val") + assert.Equal(t, []string{"b"}, imp.IframeBuster, "new-val") + }) + + t.Run("many", func(t *testing.T) { + var ( + iframeBuster = []string{"a"} + imp = &openrtb2.Imp{IframeBuster: iframeBuster} + ) + + err := MergeClone(imp, []byte(`{"iframeBuster":["b", "c"]}`)) + require.NoError(t, err) + + assert.NotSame(t, iframeBuster, imp.IframeBuster, "ref") + assert.Equal(t, []string{"a"}, iframeBuster, "original-val") + assert.Equal(t, []string{"b", "c"}, imp.IframeBuster, "new-val") + }) + + t.Run("replace-not-overlay", func(t *testing.T) { + var ( + imp = &openrtb2.Imp{ID: "1"} + impSlice = []*openrtb2.Imp{imp} + test = &struct { + Imps []*openrtb2.Imp `json:"imps"` + }{Imps: impSlice} + ) + + err := MergeClone(test, []byte(`{"imps":[{"tagid":"2"}]}`)) + require.NoError(t, err) + + impExpected := &openrtb2.Imp{TagID: "2"} // ensure original id is no longer present + assert.Equal(t, []*openrtb2.Imp{impExpected}, test.Imps) + }) + + t.Run("invalid-null", func(t *testing.T) { + var ( + iframeBuster = []string{"a"} + imp = &openrtb2.Imp{IframeBuster: iframeBuster} + ) + + err := MergeClone(imp, []byte(`{"iframeBuster":nul}`)) + + // json-iter will produce an error since "nul" is not a valid json value. the + // parsing code will see the "n" and then expect "ull" to follow. the strange + // "expect ull" error being asserted is generated by json-iter. + require.EqualError(t, err, "cannot unmarshal openrtb2.Imp.IframeBuster: expect ull") + }) + + t.Run("invalid-malformed", func(t *testing.T) { + var ( + iframeBuster = []string{"a"} + imp = &openrtb2.Imp{IframeBuster: iframeBuster} + ) + + err := MergeClone(imp, []byte(`{"iframeBuster":malformed}`)) + require.EqualError(t, err, "cannot unmarshal openrtb2.Imp.IframeBuster: decode slice: expect [ or n, but found m") + }) +} + +func TestMergeCloneMap(t *testing.T) { + t.Run("null", func(t *testing.T) { + var ( + testMap = map[string]int{"a": 1, "b": 2} + test = &struct { + Foo map[string]int `json:"foo"` + }{Foo: testMap} + ) + + err := MergeClone(test, []byte(`{"foo":null}`)) + require.NoError(t, err) + + assert.NotSame(t, testMap, test.Foo, "ref") + assert.Equal(t, map[string]int{"a": 1, "b": 2}, testMap, "val") + assert.Nil(t, test.Foo, "nil") + }) + + t.Run("key-string", func(t *testing.T) { + var ( + testMap = map[string]int{"a": 1, "b": 2} + test = &struct { + Foo map[string]int `json:"foo"` + }{Foo: testMap} + ) + + err := MergeClone(test, []byte(`{"foo":{"c":3}}`)) + require.NoError(t, err) + + assert.NotSame(t, testMap, test.Foo) + assert.Equal(t, map[string]int{"a": 1, "b": 2}, testMap, "original-val") + assert.Equal(t, map[string]int{"a": 1, "b": 2, "c": 3}, test.Foo, "new-val") + + // verify modifications don't corrupt original + testMap["a"] = 10 + assert.Equal(t, map[string]int{"a": 10, "b": 2}, testMap, "mod-original-val") + assert.Equal(t, map[string]int{"a": 1, "b": 2, "c": 3}, test.Foo, "mod-ew-val") + }) + + t.Run("key-numeric", func(t *testing.T) { + var ( + testMap = map[int]string{1: "a", 2: "b"} + test = &struct { + Foo map[int]string `json:"foo"` + }{Foo: testMap} + ) + + err := MergeClone(test, []byte(`{"foo":{"3":"c"}}`)) + require.NoError(t, err) + + assert.NotSame(t, testMap, test.Foo) + assert.Equal(t, map[int]string{1: "a", 2: "b"}, testMap, "original-val") + assert.Equal(t, map[int]string{1: "a", 2: "b", 3: "c"}, test.Foo, "new-val") + + // verify modifications don't corrupt original + testMap[1] = "z" + assert.Equal(t, map[int]string{1: "z", 2: "b"}, testMap, "mod-original-val") + assert.Equal(t, map[int]string{1: "a", 2: "b", 3: "c"}, test.Foo, "mod-ew-val") + }) + + t.Run("invalid-null", func(t *testing.T) { + var ( + testMap = map[int]string{1: "a", 2: "b"} + test = &struct { + Foo map[int]string `json:"foo"` + }{Foo: testMap} + ) + + err := MergeClone(test, []byte(`{"foo":nul}`)) + + // json-iter will produce an error since "nul" is not a valid json value. the + // parsing code will see the "n" and then expect "ull" to follow. the strange + // "expect ull" error being asserted is generated by json-iter. + require.EqualError(t, err, "cannot unmarshal Foo: expect ull") + }) + + t.Run("invalid-malformed", func(t *testing.T) { + var ( + testMap = map[int]string{1: "a", 2: "b"} + test = &struct { + Foo map[int]string `json:"foo"` + }{Foo: testMap} + ) + + err := MergeClone(test, []byte(`{"foo":malformed}`)) + require.EqualError(t, err, "cannot unmarshal Foo: expect { or n, but found m") + }) +} + +func TestMergeCloneExt(t *testing.T) { + testCases := []struct { + name string + givenExisting json.RawMessage + givenIncoming json.RawMessage + expectedExt json.RawMessage + expectedErr string + }{ + { + name: "both-populated", + givenExisting: json.RawMessage(`{"a":1,"b":2}`), + givenIncoming: json.RawMessage(`{"b":200,"c":3}`), + expectedExt: json.RawMessage(`{"a":1,"b":200,"c":3}`), + }, + { + name: "both-omitted", + givenExisting: nil, + givenIncoming: nil, + expectedExt: nil, + }, + { + name: "both-nil", + givenExisting: nil, + givenIncoming: json.RawMessage(`null`), + expectedExt: nil, + }, + { + name: "both-empty", + givenExisting: nil, + givenIncoming: json.RawMessage(`{}`), + expectedExt: json.RawMessage(`{}`), + }, + { + name: "ext-omitted", + givenExisting: json.RawMessage(`{"b":2}`), + givenIncoming: nil, + expectedExt: json.RawMessage(`{"b":2}`), + }, + { + name: "ext-nil", + givenExisting: json.RawMessage(`{"b":2}`), + givenIncoming: json.RawMessage(`null`), + expectedExt: json.RawMessage(`{"b":2}`), + }, + { + name: "ext-empty", + givenExisting: json.RawMessage(`{"b":2}`), + givenIncoming: json.RawMessage(`{}`), + expectedExt: json.RawMessage(`{"b":2}`), + }, + { + name: "ext-malformed", + givenExisting: json.RawMessage(`{"b":2}`), + givenIncoming: json.RawMessage(`malformed`), + expectedErr: "openrtb2.BidRequest.Ext", + }, + { + name: "existing-nil", + givenExisting: nil, + givenIncoming: json.RawMessage(`{"a":1}`), + expectedExt: json.RawMessage(`{"a":1}`), + }, + { + name: "existing-empty", + givenExisting: json.RawMessage(`{}`), + givenIncoming: json.RawMessage(`{"a":1}`), + expectedExt: json.RawMessage(`{"a":1}`), + }, + { + name: "existing-omitted", + givenExisting: nil, + givenIncoming: json.RawMessage(`{"b":2}`), + expectedExt: json.RawMessage(`{"b":2}`), + }, + { + name: "existing-malformed", + givenExisting: json.RawMessage(`malformed`), + givenIncoming: json.RawMessage(`{"a":1}`), + expectedErr: "cannot unmarshal openrtb2.BidRequest.Ext: invalid json on existing object", + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + // copy original values to check at the end for no modification + originalExisting := slices.Clone(test.givenExisting) + originalIncoming := slices.Clone(test.givenIncoming) + + // build request + request := &openrtb2.BidRequest{Ext: test.givenExisting} + + // build data + data := test.givenIncoming + if len(data) > 0 { + data = []byte(`{"ext":` + string(data) + `}`) // wrap in ext + } else { + data = []byte(`{}`) // omit ext + } + + err := MergeClone(request, data) + + // assert error + if test.expectedErr == "" { + assert.NoError(t, err, "err") + } else { + assert.ErrorContains(t, err, test.expectedErr, "err") + } + + // assert ext + if test.expectedErr != "" { + // expect no change in case of error + assert.Equal(t, string(test.givenExisting), string(request.Ext), "json") + } else { + // compare as strings instead of json in case of nil or malformed ext + assert.Equal(t, string(test.expectedExt), string(request.Ext), "json") + } + + // assert no modifications + // - can't use `assert.Same`` comparison checks since that's expected if + // either existing or incoming are nil / omitted / empty. + assert.Equal(t, originalExisting, test.givenExisting, "existing") + assert.Equal(t, originalIncoming, test.givenIncoming, "incoming") + }) + } +} + +func TestMergeCloneCombinations(t *testing.T) { + t.Run("slice-of-ptr", func(t *testing.T) { + var ( + imp = &openrtb2.Imp{ID: "1"} + impSlice = []*openrtb2.Imp{imp} + test = &struct { + Imps []*openrtb2.Imp `json:"imps"` + }{Imps: impSlice} + ) + + err := MergeClone(test, []byte(`{"imps":[{"id":"2"}]}`)) + require.NoError(t, err) + + assert.NotSame(t, impSlice, test.Imps, "slice-ref") + require.Len(t, test.Imps, 1, "slice-len") + + assert.NotSame(t, imp, test.Imps[0], "item-ref") + assert.Equal(t, "1", imp.ID, "original-val") + assert.Equal(t, "2", test.Imps[0].ID, "new-val") + }) + + // special case of "slice-of-ptr" + t.Run("jsonrawmessage-ptr", func(t *testing.T) { + var ( + testJson = json.RawMessage(`{"a":1}`) + test = &struct { + Foo *json.RawMessage `json:"foo"` + }{Foo: &testJson} + ) + + err := MergeClone(test, []byte(`{"foo":{"b":2}}`)) + require.NoError(t, err) + + assert.NotSame(t, &testJson, test.Foo, "ref") + assert.Equal(t, json.RawMessage(`{"a":1}`), testJson) + assert.Equal(t, json.RawMessage(`{"a":1,"b":2}`), *test.Foo) + }) + + t.Run("struct-ptr", func(t *testing.T) { + var ( + imp = &openrtb2.Imp{ID: "1"} + test = &struct { + Imp *openrtb2.Imp `json:"imp"` + }{Imp: imp} + ) + + err := MergeClone(test, []byte(`{"imp":{"id":"2"}}`)) + require.NoError(t, err) + + assert.NotSame(t, imp, test.Imp, "ref") + assert.Equal(t, "1", imp.ID, "original-val") + assert.Equal(t, "2", test.Imp.ID, "new-val") + }) + + t.Run("map-of-ptrs", func(t *testing.T) { + var ( + imp = &openrtb2.Imp{ID: "1"} + impMap = map[string]*openrtb2.Imp{"a": imp} + test = &struct { + Imps map[string]*openrtb2.Imp `json:"imps"` + }{Imps: impMap} + ) + + err := MergeClone(test, []byte(`{"imps":{"a":{"id":"2"}}}`)) + require.NoError(t, err) + + assert.NotSame(t, impMap, test.Imps, "map-ref") + assert.NotSame(t, imp, test.Imps["a"], "imp-ref") + + assert.Same(t, impMap["a"], imp, "imp-map-ref") + + assert.Equal(t, "1", imp.ID, "original-val") + assert.Equal(t, "2", test.Imps["a"].ID, "new-val") + }) +} diff --git a/util/maputil/maputil.go b/util/maputil/maputil.go index c33740d456c..49df0772ba6 100644 --- a/util/maputil/maputil.go +++ b/util/maputil/maputil.go @@ -48,16 +48,3 @@ func HasElement(m map[string]interface{}, k ...string) bool { return exists } - -// Clone creates an indepent copy of a map, -func Clone[K comparable, V any](m map[K]V) map[K]V { - if m == nil { - return nil - } - clone := make(map[K]V, len(m)) - for key, value := range m { - clone[key] = value - } - - return clone -} diff --git a/util/maputil/maputil_test.go b/util/maputil/maputil_test.go index e7c067224c7..114c23327bd 100644 --- a/util/maputil/maputil_test.go +++ b/util/maputil/maputil_test.go @@ -245,29 +245,3 @@ func TestHasElement(t *testing.T) { assert.Equal(t, test.expected, result, test.description) } } - -func TestCloneMap(t *testing.T) { - // Test we handle nils properly - t.Run("NilMap", func(t *testing.T) { - var testMap, copyMap map[string]string = nil, nil // copyMap is a manual copy of testMap - clone := Clone(testMap) - testMap = map[string]string{"foo": "bar"} - assert.Equal(t, copyMap, clone) - }) - // Test a simple string map - t.Run("StringMap", func(t *testing.T) { - var testMap, copyMap map[string]string = map[string]string{"foo": "bar", "first": "one"}, map[string]string{"foo": "bar", "first": "one"} - clone := Clone(testMap) - testMap["foo"] = "baz" - testMap["bozo"] = "the clown" - assert.Equal(t, copyMap, clone) - }) - // Test a simple map[string]int - t.Run("StringInt", func(t *testing.T) { - var testMap, copyMap map[string]int = map[string]int{"foo": 1, "first": 2}, map[string]int{"foo": 1, "first": 2} - clone := Clone(testMap) - testMap["foo"] = 7 - testMap["bozo"] = 13 - assert.Equal(t, copyMap, clone) - }) -} diff --git a/util/ptrutil/ptrutil.go b/util/ptrutil/ptrutil.go index 90888f3eef8..27616a0a4b5 100644 --- a/util/ptrutil/ptrutil.go +++ b/util/ptrutil/ptrutil.go @@ -12,3 +12,12 @@ func Clone[T any](v *T) *T { clone := *v return &clone } + +func ValueOrDefault[T any](v *T) T { + if v != nil { + return *v + } + + var def T + return def +} diff --git a/util/ptrutil/ptrutil_test.go b/util/ptrutil/ptrutil_test.go new file mode 100644 index 00000000000..85671e31b44 --- /dev/null +++ b/util/ptrutil/ptrutil_test.go @@ -0,0 +1,45 @@ +package ptrutil + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestValueOrDefault(t *testing.T) { + t.Run("int-nil", func(t *testing.T) { + var v *int = nil + r := ValueOrDefault(v) + assert.Equal(t, 0, r) + }) + + t.Run("int-0", func(t *testing.T) { + var v *int = ToPtr[int](0) + r := ValueOrDefault(v) + assert.Equal(t, 0, r) + }) + + t.Run("int-42", func(t *testing.T) { + var v *int = ToPtr[int](42) + r := ValueOrDefault(v) + assert.Equal(t, 42, r) + }) + + t.Run("string-nil", func(t *testing.T) { + var v *string = nil + r := ValueOrDefault(v) + assert.Equal(t, "", r) + }) + + t.Run("string-empty", func(t *testing.T) { + var v *string = ToPtr[string]("") + r := ValueOrDefault(v) + assert.Equal(t, "", r) + }) + + t.Run("string-something", func(t *testing.T) { + var v *string = ToPtr[string]("something") + r := ValueOrDefault(v) + assert.Equal(t, "something", r) + }) +} diff --git a/util/sliceutil/clone.go b/util/sliceutil/clone.go deleted file mode 100644 index 2077a9336b2..00000000000 --- a/util/sliceutil/clone.go +++ /dev/null @@ -1,12 +0,0 @@ -package sliceutil - -func Clone[T any](s []T) []T { - if s == nil { - return nil - } - - c := make([]T, len(s)) - copy(c, s) - - return c -} diff --git a/util/sliceutil/clone_test.go b/util/sliceutil/clone_test.go deleted file mode 100644 index 6e905b5cbe1..00000000000 --- a/util/sliceutil/clone_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package sliceutil - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCloneSlice(t *testing.T) { - testCases := []struct { - name string - given []int - }{ - { - name: "nil", - given: nil, - }, - { - name: "empty", - given: []int{}, - }, - { - name: "one", - given: []int{1}, - }, - { - name: "many", - given: []int{1, 2}, - }, - } - - for _, test := range testCases { - t.Run(test.name, func(t *testing.T) { - result := Clone(test.given) - assert.Equal(t, test.given, result, "equality") - assert.NotSame(t, test.given, result, "pointer") - }) - } -} diff --git a/util/stringutil/stringutil_test.go b/util/stringutil/stringutil_test.go index 94988ee41c9..a7aa0010995 100644 --- a/util/stringutil/stringutil_test.go +++ b/util/stringutil/stringutil_test.go @@ -30,7 +30,7 @@ func TestStrToInt8Slice(t *testing.T) { in: "malformed", expected: testOutput{ arr: nil, - err: &strconv.NumError{"ParseInt", "malformed", strconv.ErrSyntax}, + err: &strconv.NumError{Func: "ParseInt", Num: "malformed", Err: strconv.ErrSyntax}, }, }, { @@ -38,7 +38,7 @@ func TestStrToInt8Slice(t *testing.T) { in: "malformed,2,malformed", expected: testOutput{ arr: nil, - err: &strconv.NumError{"ParseInt", "malformed", strconv.ErrSyntax}, + err: &strconv.NumError{Func: "ParseInt", Num: "malformed", Err: strconv.ErrSyntax}, }, }, { @@ -46,7 +46,7 @@ func TestStrToInt8Slice(t *testing.T) { in: "128", expected: testOutput{ arr: nil, - err: &strconv.NumError{"ParseInt", "128", strconv.ErrRange}, + err: &strconv.NumError{Func: "ParseInt", Num: "128", Err: strconv.ErrRange}, }, }, { diff --git a/util/task/ticker_task_test.go b/util/task/ticker_task_test.go index c02eec158a1..b1acb35ffa1 100644 --- a/util/task/ticker_task_test.go +++ b/util/task/ticker_task_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/util/task" + "github.com/prebid/prebid-server/v3/util/task" "github.com/stretchr/testify/assert" ) diff --git a/validate.sh b/validate.sh index d263df0ab51..ca4fbc1dfd7 100755 --- a/validate.sh +++ b/validate.sh @@ -36,5 +36,5 @@ fi if $VET; then echo "Running go vet check" - go vet -composites=false ./... + go vet ./... fi diff --git a/version/xprebidheader.go b/version/xprebidheader.go index fc71bacb1a3..7e31aa4b112 100644 --- a/version/xprebidheader.go +++ b/version/xprebidheader.go @@ -3,9 +3,9 @@ package version import ( "strings" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) const xPrebidHeaderVersionPrefix = "pbs-go" diff --git a/version/xprebidheader_test.go b/version/xprebidheader_test.go index a1c7b355bb8..15a73fe98f8 100644 --- a/version/xprebidheader_test.go +++ b/version/xprebidheader_test.go @@ -3,11 +3,11 @@ package version import ( "testing" - "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/openrtb/v20/openrtb2" "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/util/jsonutil" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" ) func TestBuildXPrebidHeader(t *testing.T) { From 9358bdf6ee59f60268b51022b9c41cc3ea7ec44b Mon Sep 17 00:00:00 2001 From: Meet Shah Date: Thu, 27 Mar 2025 15:09:32 +0530 Subject: [PATCH 14/17] infy version updated --- adapters/infytvhb/infytvhb.go | 22 +++++++++++++++++----- adapters/infytvhb/infytvhb_test.go | 6 +++--- adapters/infytvhb/params_test.go | 2 +- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/adapters/infytvhb/infytvhb.go b/adapters/infytvhb/infytvhb.go index c6eba3e77b5..a5aff130857 100644 --- a/adapters/infytvhb/infytvhb.go +++ b/adapters/infytvhb/infytvhb.go @@ -5,11 +5,11 @@ import ( "fmt" "net/http" - "github.com/prebid/openrtb/v19/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) type adapter struct { @@ -84,10 +84,14 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte } } + fmt.Printf("request: %v\n", requests) + fmt.Printf("errors: %v\n", errors) return requests, errors } func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + fmt.Printf("response: %v\n", response) + fmt.Printf("response.Body: %v\n", string(response.Body)) if response.StatusCode == http.StatusNoContent { return nil, nil } else if response.StatusCode == http.StatusBadRequest { @@ -100,10 +104,14 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest }} } + fmt.Printf("internalRequest.Imp: %v\n", internalRequest.Imp) + if len(internalRequest.Imp) > 0 { var bidResp openrtb2.BidResponse impression := &internalRequest.Imp[0] if infyExt, err := getImpressionExt(impression); err == nil { + fmt.Printf("infyExt.EndpointID: %v\n", infyExt.EndpointID) + fmt.Printf("infyExt.EndpointType: %v\n", infyExt.EndpointType) if infyExt.EndpointType == "VAST_URL" { bidResp = openrtb2.BidResponse{ ID: internalRequest.ID, @@ -125,6 +133,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest } } else { if err := json.Unmarshal(response.Body, &bidResp); err != nil { + fmt.Printf("err: %v\n", err) return nil, []error{err} } for i, sb := range bidResp.SeatBid { @@ -140,6 +149,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest } } } + fmt.Printf("impression: %v\n", impression) bidsCapacity := 1 if len(bidResp.SeatBid) > 0 { bidsCapacity = len(bidResp.SeatBid[0].Bid) @@ -157,6 +167,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest } } } + fmt.Printf("bidResponse: %v\n", bidResponse) return bidResponse, nil } @@ -181,6 +192,7 @@ func getMediaTypeForBid(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { // getImpressionExt parses and return first imp ext or nil func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtInfytvHb, error) { var bidderExt adapters.ExtImpBidder + fmt.Printf("imp.Ext: %v\n", imp.Ext) if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ Message: err.Error(), diff --git a/adapters/infytvhb/infytvhb_test.go b/adapters/infytvhb/infytvhb_test.go index 9ca6d088015..43f817c8643 100644 --- a/adapters/infytvhb/infytvhb_test.go +++ b/adapters/infytvhb/infytvhb_test.go @@ -3,9 +3,9 @@ package infytvhb import ( "testing" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/adapters/adapterstest" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/infytvhb/params_test.go b/adapters/infytvhb/params_test.go index beba085a64c..bf0551eaa73 100644 --- a/adapters/infytvhb/params_test.go +++ b/adapters/infytvhb/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/v3/openrtb_ext" ) func TestValidParams(t *testing.T) { From 72985cc525b0a2913ae16e719f9780ad45317812 Mon Sep 17 00:00:00 2001 From: Meet Shah Date: Thu, 3 Apr 2025 19:03:47 +0530 Subject: [PATCH 15/17] dsp_config added --- adapters/infytvhb/infytvhb.go | 76 +++++++++++++++++++++++++++++++++++ openrtb_ext/imp_infytvhb.go | 36 +++++++++++------ 2 files changed, 100 insertions(+), 12 deletions(-) diff --git a/adapters/infytvhb/infytvhb.go b/adapters/infytvhb/infytvhb.go index a5aff130857..56488e3f07d 100644 --- a/adapters/infytvhb/infytvhb.go +++ b/adapters/infytvhb/infytvhb.go @@ -4,7 +4,10 @@ import ( "encoding/json" "fmt" "net/http" + "regexp" + "strings" + "github.com/buger/jsonparser" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v3/adapters" "github.com/prebid/prebid-server/v3/config" @@ -62,6 +65,8 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte reqCopy.Imp = append(reqCopy.Imp, imp) reqCopy.Ext = nil requestJSON, err := json.Marshal(reqCopy) + dynamicConfigMerge(infyExt.DspConfigs, &requestJSON, &endpoint) + // dynamic config map if err != nil { errors = append(errors, err) continue @@ -207,3 +212,74 @@ func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtInfytvHb, error) { } return &extImpInfyTV, nil } + +func dynamicConfigMerge(dcs []openrtb_ext.DspConfig, request *[]byte, endpoint *string) { + for _, dc := range dcs { + switch dc.ConditionType { + case "geo": + continue + case "bundles": + dynamicConfigMergeBundles(dc.Conditions, request, endpoint) + default: + continue + } + } +} + +func dynamicConfigMergeBundles(Conditions map[string][]openrtb_ext.DspConfigValueEntry, request *[]byte, endpoint *string) { + siteDomain, _ := jsonparser.GetString(*request, "site", "domain") + appBundleID, _ := jsonparser.GetString(*request, "app", "bundle") + + for bundleId, con := range Conditions { + if (siteDomain != "" && bundleId == "site") || (siteDomain == bundleId) { + dynamicConfigMergeValues(con, request, endpoint, "site") + } else if appBundleID == bundleId { + dynamicConfigMergeValues(con, request, endpoint, "app") + } + } +} + +func ConvertPath(jsonPath string) []string { + // Regular expression to capture keys and array indices separately + re := regexp.MustCompile(`\w+|\[\d+\]`) + + // Find all matches in the given path + matches := re.FindAllString(jsonPath, -1) + + return matches +} + +func dynamicConfigMergeValues(con []openrtb_ext.DspConfigValueEntry, requestJson *[]byte, endpoint *string, t string) { + for _, v := range con { + switch v.Place { + case "body": + for key, value := range v.KeyMap { + dynamicVal := strings.HasPrefix(value, "Vx") + if dynamicVal { + value = strings.TrimPrefix(value, "Vx") + dynamicKeys := ConvertPath(value) + var err error + value, err = jsonparser.GetUnsafeString(*requestJson, dynamicKeys...) + if err != nil { + continue + } + } + keys := ConvertPath(key) + if (t == "app" && keys[0] == "site") || (t == "site" && keys[0] == "app") { + continue + } + if newJson, err := jsonparser.Set(*requestJson, []byte(value), keys...); err == nil { + requestJson = &newJson + } + } + case "queryparam": + continue + case "url": + continue + default: + continue + } + } + + fmt.Printf("requestJson:: %v\n", string(*requestJson)) +} diff --git a/openrtb_ext/imp_infytvhb.go b/openrtb_ext/imp_infytvhb.go index fb7644e4a28..18dec45c314 100644 --- a/openrtb_ext/imp_infytvhb.go +++ b/openrtb_ext/imp_infytvhb.go @@ -1,16 +1,28 @@ package openrtb_ext +type DspConfig struct { + ConditionType string `json:"condition_type"` + Conditions map[string][]DspConfigValueEntry `json:"conditions"` +} + +// DspConfigValueEntry represents the value entry inside the `values` map +type DspConfigValueEntry struct { + KeyMap map[string]string `json:"key_map"` + Place string `json:"place"` +} + type ExtInfytvHb struct { - DspID string `json:"dsp_id"` - CustomerID string `json:"customer_id"` - TagID string `json:"tag_id"` - EndpointID string `json:"endpoint_id"` - DealID string `json:"deal_id"` - Base string `json:"base"` - Path string `json:"path"` - DspType string `json:"dsp_type"` - MinCpm float64 `json:"min_cpm"` - MaxCpm float64 `json:"max_cpm"` - EndpointType string `json:"type"` - Floor float64 `json:"floor_price"` + DspID string `json:"dsp_id"` + CustomerID string `json:"customer_id"` + TagID string `json:"tag_id"` + EndpointID string `json:"endpoint_id"` + DealID string `json:"deal_id"` + Base string `json:"base"` + Path string `json:"path"` + DspType string `json:"dsp_type"` + MinCpm float64 `json:"min_cpm"` + MaxCpm float64 `json:"max_cpm"` + EndpointType string `json:"type"` + DspConfigs []DspConfig `json:"dsp_configs"` + Floor float64 `json:"floor_price"` } From e05884b615e8076ad4fcb3bff37397492ab2cbd3 Mon Sep 17 00:00:00 2001 From: Meet Shah Date: Tue, 8 Apr 2025 18:48:07 +0530 Subject: [PATCH 16/17] media.net log added --- adapters/medianet/medianet.go | 1 + 1 file changed, 1 insertion(+) diff --git a/adapters/medianet/medianet.go b/adapters/medianet/medianet.go index 08a1ae021f4..8f04405649f 100644 --- a/adapters/medianet/medianet.go +++ b/adapters/medianet/medianet.go @@ -22,6 +22,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E var errs []error reqJson, err := json.Marshal(request) + fmt.Printf("reqJson: %v\n", string(reqJson)) if err != nil { errs = append(errs, err) return nil, errs From af8d191c7e3e1418f67d6acd2d5c4c4942a5a838 Mon Sep 17 00:00:00 2001 From: Meet Shah Date: Wed, 9 Apr 2025 11:34:58 +0530 Subject: [PATCH 17/17] Update infytvhb.go --- adapters/infytvhb/infytvhb.go | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/adapters/infytvhb/infytvhb.go b/adapters/infytvhb/infytvhb.go index 56488e3f07d..bd045c1d75e 100644 --- a/adapters/infytvhb/infytvhb.go +++ b/adapters/infytvhb/infytvhb.go @@ -65,7 +65,8 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte reqCopy.Imp = append(reqCopy.Imp, imp) reqCopy.Ext = nil requestJSON, err := json.Marshal(reqCopy) - dynamicConfigMerge(infyExt.DspConfigs, &requestJSON, &endpoint) + requestJSON = *(dynamicConfigMerge(infyExt.DspConfigs, &requestJSON, &endpoint)) + fmt.Printf("requestJSON: %v\n", string(requestJSON)) // dynamic config map if err != nil { errors = append(errors, err) @@ -89,14 +90,10 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte } } - fmt.Printf("request: %v\n", requests) - fmt.Printf("errors: %v\n", errors) return requests, errors } func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - fmt.Printf("response: %v\n", response) - fmt.Printf("response.Body: %v\n", string(response.Body)) if response.StatusCode == http.StatusNoContent { return nil, nil } else if response.StatusCode == http.StatusBadRequest { @@ -109,14 +106,10 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest }} } - fmt.Printf("internalRequest.Imp: %v\n", internalRequest.Imp) - if len(internalRequest.Imp) > 0 { var bidResp openrtb2.BidResponse impression := &internalRequest.Imp[0] if infyExt, err := getImpressionExt(impression); err == nil { - fmt.Printf("infyExt.EndpointID: %v\n", infyExt.EndpointID) - fmt.Printf("infyExt.EndpointType: %v\n", infyExt.EndpointType) if infyExt.EndpointType == "VAST_URL" { bidResp = openrtb2.BidResponse{ ID: internalRequest.ID, @@ -138,7 +131,6 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest } } else { if err := json.Unmarshal(response.Body, &bidResp); err != nil { - fmt.Printf("err: %v\n", err) return nil, []error{err} } for i, sb := range bidResp.SeatBid { @@ -154,7 +146,6 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest } } } - fmt.Printf("impression: %v\n", impression) bidsCapacity := 1 if len(bidResp.SeatBid) > 0 { bidsCapacity = len(bidResp.SeatBid[0].Bid) @@ -172,7 +163,6 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest } } } - fmt.Printf("bidResponse: %v\n", bidResponse) return bidResponse, nil } @@ -197,7 +187,6 @@ func getMediaTypeForBid(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { // getImpressionExt parses and return first imp ext or nil func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtInfytvHb, error) { var bidderExt adapters.ExtImpBidder - fmt.Printf("imp.Ext: %v\n", imp.Ext) if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ Message: err.Error(), @@ -213,30 +202,32 @@ func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtInfytvHb, error) { return &extImpInfyTV, nil } -func dynamicConfigMerge(dcs []openrtb_ext.DspConfig, request *[]byte, endpoint *string) { +func dynamicConfigMerge(dcs []openrtb_ext.DspConfig, request *[]byte, endpoint *string) *[]byte { for _, dc := range dcs { switch dc.ConditionType { case "geo": continue case "bundles": - dynamicConfigMergeBundles(dc.Conditions, request, endpoint) + request = dynamicConfigMergeBundles(dc.Conditions, request, endpoint) default: continue } } + return request } -func dynamicConfigMergeBundles(Conditions map[string][]openrtb_ext.DspConfigValueEntry, request *[]byte, endpoint *string) { +func dynamicConfigMergeBundles(Conditions map[string][]openrtb_ext.DspConfigValueEntry, request *[]byte, endpoint *string) *[]byte { siteDomain, _ := jsonparser.GetString(*request, "site", "domain") appBundleID, _ := jsonparser.GetString(*request, "app", "bundle") for bundleId, con := range Conditions { if (siteDomain != "" && bundleId == "site") || (siteDomain == bundleId) { - dynamicConfigMergeValues(con, request, endpoint, "site") + request = dynamicConfigMergeValues(con, request, endpoint, "site") } else if appBundleID == bundleId { - dynamicConfigMergeValues(con, request, endpoint, "app") + request = dynamicConfigMergeValues(con, request, endpoint, "app") } } + return request } func ConvertPath(jsonPath string) []string { @@ -249,7 +240,7 @@ func ConvertPath(jsonPath string) []string { return matches } -func dynamicConfigMergeValues(con []openrtb_ext.DspConfigValueEntry, requestJson *[]byte, endpoint *string, t string) { +func dynamicConfigMergeValues(con []openrtb_ext.DspConfigValueEntry, requestJson *[]byte, endpoint *string, t string) *[]byte { for _, v := range con { switch v.Place { case "body": @@ -280,6 +271,5 @@ func dynamicConfigMergeValues(con []openrtb_ext.DspConfigValueEntry, requestJson continue } } - - fmt.Printf("requestJson:: %v\n", string(*requestJson)) + return requestJson }