From 3dc5e77e5b7564ac45bd7617b823b808b669121c Mon Sep 17 00:00:00 2001 From: Oleksandr Zhevedenko <720803+Net-burst@users.noreply.github.com> Date: Wed, 10 Dec 2025 11:23:19 -0500 Subject: [PATCH 01/38] Prebid Server prepare release 3.37.0 --- extra/bundle/pom.xml | 2 +- extra/modules/confiant-ad-quality/pom.xml | 2 +- extra/modules/fiftyone-devicedetection/pom.xml | 2 +- extra/modules/greenbids-real-time-data/pom.xml | 2 +- extra/modules/live-intent-omni-channel-identity/pom.xml | 2 +- extra/modules/optable-targeting/pom.xml | 2 +- extra/modules/ortb2-blocking/pom.xml | 2 +- extra/modules/pb-request-correction/pom.xml | 2 +- extra/modules/pb-response-correction/pom.xml | 2 +- extra/modules/pb-richmedia-filter/pom.xml | 2 +- extra/modules/pb-rule-engine/pom.xml | 2 +- extra/modules/pom.xml | 2 +- extra/modules/wurfl-devicedetection/pom.xml | 2 +- extra/pom.xml | 4 ++-- pom.xml | 2 +- 15 files changed, 16 insertions(+), 16 deletions(-) diff --git a/extra/bundle/pom.xml b/extra/bundle/pom.xml index e5ecd041937..65f884959dc 100644 --- a/extra/bundle/pom.xml +++ b/extra/bundle/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.37.0-SNAPSHOT + 3.37.0 ../../extra/pom.xml diff --git a/extra/modules/confiant-ad-quality/pom.xml b/extra/modules/confiant-ad-quality/pom.xml index 62be75c4bd3..b63a0ba8511 100644 --- a/extra/modules/confiant-ad-quality/pom.xml +++ b/extra/modules/confiant-ad-quality/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.37.0 confiant-ad-quality diff --git a/extra/modules/fiftyone-devicedetection/pom.xml b/extra/modules/fiftyone-devicedetection/pom.xml index 04447c1e2e8..9fce1beed84 100644 --- a/extra/modules/fiftyone-devicedetection/pom.xml +++ b/extra/modules/fiftyone-devicedetection/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.37.0 fiftyone-devicedetection diff --git a/extra/modules/greenbids-real-time-data/pom.xml b/extra/modules/greenbids-real-time-data/pom.xml index bcacf785cc8..478f471a741 100644 --- a/extra/modules/greenbids-real-time-data/pom.xml +++ b/extra/modules/greenbids-real-time-data/pom.xml @@ -4,7 +4,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.37.0 greenbids-real-time-data diff --git a/extra/modules/live-intent-omni-channel-identity/pom.xml b/extra/modules/live-intent-omni-channel-identity/pom.xml index 1e16b624733..e5207879b8a 100644 --- a/extra/modules/live-intent-omni-channel-identity/pom.xml +++ b/extra/modules/live-intent-omni-channel-identity/pom.xml @@ -4,7 +4,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.37.0 live-intent-omni-channel-identity diff --git a/extra/modules/optable-targeting/pom.xml b/extra/modules/optable-targeting/pom.xml index 0e6b0622299..0ed012774c3 100644 --- a/extra/modules/optable-targeting/pom.xml +++ b/extra/modules/optable-targeting/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.37.0 optable-targeting diff --git a/extra/modules/ortb2-blocking/pom.xml b/extra/modules/ortb2-blocking/pom.xml index 32c26829d83..75cb0a37dd2 100644 --- a/extra/modules/ortb2-blocking/pom.xml +++ b/extra/modules/ortb2-blocking/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.37.0 ortb2-blocking diff --git a/extra/modules/pb-request-correction/pom.xml b/extra/modules/pb-request-correction/pom.xml index 0e6297e934b..c2836841410 100644 --- a/extra/modules/pb-request-correction/pom.xml +++ b/extra/modules/pb-request-correction/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.37.0 pb-request-correction diff --git a/extra/modules/pb-response-correction/pom.xml b/extra/modules/pb-response-correction/pom.xml index 6fda39fe4ec..c07fa47126f 100644 --- a/extra/modules/pb-response-correction/pom.xml +++ b/extra/modules/pb-response-correction/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.37.0 pb-response-correction diff --git a/extra/modules/pb-richmedia-filter/pom.xml b/extra/modules/pb-richmedia-filter/pom.xml index 27a17ea0009..b188432710c 100644 --- a/extra/modules/pb-richmedia-filter/pom.xml +++ b/extra/modules/pb-richmedia-filter/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.37.0 pb-richmedia-filter diff --git a/extra/modules/pb-rule-engine/pom.xml b/extra/modules/pb-rule-engine/pom.xml index a8b4caa752f..a2013ffccfb 100644 --- a/extra/modules/pb-rule-engine/pom.xml +++ b/extra/modules/pb-rule-engine/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.37.0 pb-rule-engine diff --git a/extra/modules/pom.xml b/extra/modules/pom.xml index 50eb19cdee5..7808e5f3764 100644 --- a/extra/modules/pom.xml +++ b/extra/modules/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.37.0-SNAPSHOT + 3.37.0 ../../extra/pom.xml diff --git a/extra/modules/wurfl-devicedetection/pom.xml b/extra/modules/wurfl-devicedetection/pom.xml index 17a99a407cf..18f0cc4103c 100644 --- a/extra/modules/wurfl-devicedetection/pom.xml +++ b/extra/modules/wurfl-devicedetection/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0-SNAPSHOT + 3.37.0 wurfl-devicedetection diff --git a/extra/pom.xml b/extra/pom.xml index 6e310f2dedb..2745651b5e5 100644 --- a/extra/pom.xml +++ b/extra/pom.xml @@ -4,14 +4,14 @@ org.prebid prebid-server-aggregator - 3.37.0-SNAPSHOT + 3.37.0 pom https://github.com/prebid/prebid-server-java scm:git:git@github.com:prebid/prebid-server-java.git scm:git:git@github.com:prebid/prebid-server-java.git - HEAD + 3.37.0 diff --git a/pom.xml b/pom.xml index 750ce6efa65..a8d6308d2c6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.37.0-SNAPSHOT + 3.37.0 extra/pom.xml From 01acd952715516a4c80be35f5f1d7fa915e635ad Mon Sep 17 00:00:00 2001 From: Oleksandr Zhevedenko <720803+Net-burst@users.noreply.github.com> Date: Wed, 10 Dec 2025 11:23:19 -0500 Subject: [PATCH 02/38] Prebid Server prepare for next development iteration --- extra/bundle/pom.xml | 2 +- extra/modules/confiant-ad-quality/pom.xml | 2 +- extra/modules/fiftyone-devicedetection/pom.xml | 2 +- extra/modules/greenbids-real-time-data/pom.xml | 2 +- extra/modules/live-intent-omni-channel-identity/pom.xml | 2 +- extra/modules/optable-targeting/pom.xml | 2 +- extra/modules/ortb2-blocking/pom.xml | 2 +- extra/modules/pb-request-correction/pom.xml | 2 +- extra/modules/pb-response-correction/pom.xml | 2 +- extra/modules/pb-richmedia-filter/pom.xml | 2 +- extra/modules/pb-rule-engine/pom.xml | 2 +- extra/modules/pom.xml | 2 +- extra/modules/wurfl-devicedetection/pom.xml | 2 +- extra/pom.xml | 4 ++-- pom.xml | 2 +- 15 files changed, 16 insertions(+), 16 deletions(-) diff --git a/extra/bundle/pom.xml b/extra/bundle/pom.xml index 65f884959dc..ae599096627 100644 --- a/extra/bundle/pom.xml +++ b/extra/bundle/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.37.0 + 3.38.0-SNAPSHOT ../../extra/pom.xml diff --git a/extra/modules/confiant-ad-quality/pom.xml b/extra/modules/confiant-ad-quality/pom.xml index b63a0ba8511..4059db432d5 100644 --- a/extra/modules/confiant-ad-quality/pom.xml +++ b/extra/modules/confiant-ad-quality/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0 + 3.38.0-SNAPSHOT confiant-ad-quality diff --git a/extra/modules/fiftyone-devicedetection/pom.xml b/extra/modules/fiftyone-devicedetection/pom.xml index 9fce1beed84..544033cca2b 100644 --- a/extra/modules/fiftyone-devicedetection/pom.xml +++ b/extra/modules/fiftyone-devicedetection/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0 + 3.38.0-SNAPSHOT fiftyone-devicedetection diff --git a/extra/modules/greenbids-real-time-data/pom.xml b/extra/modules/greenbids-real-time-data/pom.xml index 478f471a741..61da977278d 100644 --- a/extra/modules/greenbids-real-time-data/pom.xml +++ b/extra/modules/greenbids-real-time-data/pom.xml @@ -4,7 +4,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0 + 3.38.0-SNAPSHOT greenbids-real-time-data diff --git a/extra/modules/live-intent-omni-channel-identity/pom.xml b/extra/modules/live-intent-omni-channel-identity/pom.xml index e5207879b8a..f6782ff201d 100644 --- a/extra/modules/live-intent-omni-channel-identity/pom.xml +++ b/extra/modules/live-intent-omni-channel-identity/pom.xml @@ -4,7 +4,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0 + 3.38.0-SNAPSHOT live-intent-omni-channel-identity diff --git a/extra/modules/optable-targeting/pom.xml b/extra/modules/optable-targeting/pom.xml index 0ed012774c3..03383e72ffe 100644 --- a/extra/modules/optable-targeting/pom.xml +++ b/extra/modules/optable-targeting/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0 + 3.38.0-SNAPSHOT optable-targeting diff --git a/extra/modules/ortb2-blocking/pom.xml b/extra/modules/ortb2-blocking/pom.xml index 75cb0a37dd2..d904007722b 100644 --- a/extra/modules/ortb2-blocking/pom.xml +++ b/extra/modules/ortb2-blocking/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0 + 3.38.0-SNAPSHOT ortb2-blocking diff --git a/extra/modules/pb-request-correction/pom.xml b/extra/modules/pb-request-correction/pom.xml index c2836841410..b4dda3e3c35 100644 --- a/extra/modules/pb-request-correction/pom.xml +++ b/extra/modules/pb-request-correction/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0 + 3.38.0-SNAPSHOT pb-request-correction diff --git a/extra/modules/pb-response-correction/pom.xml b/extra/modules/pb-response-correction/pom.xml index c07fa47126f..0770b766e5b 100644 --- a/extra/modules/pb-response-correction/pom.xml +++ b/extra/modules/pb-response-correction/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0 + 3.38.0-SNAPSHOT pb-response-correction diff --git a/extra/modules/pb-richmedia-filter/pom.xml b/extra/modules/pb-richmedia-filter/pom.xml index b188432710c..6ddd11efcf7 100644 --- a/extra/modules/pb-richmedia-filter/pom.xml +++ b/extra/modules/pb-richmedia-filter/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0 + 3.38.0-SNAPSHOT pb-richmedia-filter diff --git a/extra/modules/pb-rule-engine/pom.xml b/extra/modules/pb-rule-engine/pom.xml index a2013ffccfb..e23c6767817 100644 --- a/extra/modules/pb-rule-engine/pom.xml +++ b/extra/modules/pb-rule-engine/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0 + 3.38.0-SNAPSHOT pb-rule-engine diff --git a/extra/modules/pom.xml b/extra/modules/pom.xml index 7808e5f3764..6c4e70d62ab 100644 --- a/extra/modules/pom.xml +++ b/extra/modules/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.37.0 + 3.38.0-SNAPSHOT ../../extra/pom.xml diff --git a/extra/modules/wurfl-devicedetection/pom.xml b/extra/modules/wurfl-devicedetection/pom.xml index 18f0cc4103c..555ea37fdce 100644 --- a/extra/modules/wurfl-devicedetection/pom.xml +++ b/extra/modules/wurfl-devicedetection/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.37.0 + 3.38.0-SNAPSHOT wurfl-devicedetection diff --git a/extra/pom.xml b/extra/pom.xml index 2745651b5e5..c41bd328043 100644 --- a/extra/pom.xml +++ b/extra/pom.xml @@ -4,14 +4,14 @@ org.prebid prebid-server-aggregator - 3.37.0 + 3.38.0-SNAPSHOT pom https://github.com/prebid/prebid-server-java scm:git:git@github.com:prebid/prebid-server-java.git scm:git:git@github.com:prebid/prebid-server-java.git - 3.37.0 + HEAD diff --git a/pom.xml b/pom.xml index a8d6308d2c6..0c79bae025d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.37.0 + 3.38.0-SNAPSHOT extra/pom.xml From 033af314a79638c214cdbac6315d4b79f95159bd Mon Sep 17 00:00:00 2001 From: Victor Malitskyi Date: Mon, 22 Dec 2025 15:42:12 +0100 Subject: [PATCH 03/38] Fix vulnerability in cross-repo-issue GitHub action (#4324) --- .github/workflows/cross-repo-issue.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cross-repo-issue.yml b/.github/workflows/cross-repo-issue.yml index c2288da271a..a2aa9471ecf 100644 --- a/.github/workflows/cross-repo-issue.yml +++ b/.github/workflows/cross-repo-issue.yml @@ -23,9 +23,10 @@ jobs: github.event.pull_request.merged env: GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + PR_TITLE: ${{ github.event.pull_request.title }} run: | echo -e "A PR was merged over on PBS-Java\n\n- [https://github.com/prebid/prebid-server-java/pull/${{github.event.number}}](https://github.com/prebid/prebid-server-java/pull/${{github.event.number}})\n- timestamp: ${{ github.event.pull_request.merged_at}}" > msg export msg=$(cat msg) - gh issue create --repo prebid/prebid-server --title "Port PR from PBS-Java: ${{ github.event.pull_request.title }}" \ + gh issue create --repo prebid/prebid-server --title "Port PR from PBS-Java: $PR_TITLE" \ --body "$msg" \ --label auto From 26f4f7cb169de5990448b52b4f5ae639b7a6ba9d Mon Sep 17 00:00:00 2001 From: osulzhenko <125548596+osulzhenko@users.noreply.github.com> Date: Mon, 12 Jan 2026 14:59:03 +0200 Subject: [PATCH 04/38] Optidigital adapter: Add GPP support (#4333) --- src/main/resources/bidder-config/optidigital.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/bidder-config/optidigital.yaml b/src/main/resources/bidder-config/optidigital.yaml index 9e48244221b..756e0d71b2e 100644 --- a/src/main/resources/bidder-config/optidigital.yaml +++ b/src/main/resources/bidder-config/optidigital.yaml @@ -17,6 +17,6 @@ adapters: usersync: cookie-family-name: optidigital iframe: - url: https://scripts.opti-digital.com/js/presyncs2s.html?endpoint=optidigital&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redir={{redirect_url}} + url: https://scripts.opti-digital.com/js/presyncs2s.html?endpoint=optidigital&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&redir={{redirect_url}} support-cors: false uid-macro: '$UID' From 9d6dfb9bfbf0f34d22d69bf2b2cc13db95e018a8 Mon Sep 17 00:00:00 2001 From: przemkaczmarek <167743744+przemkaczmarek@users.noreply.github.com> Date: Mon, 12 Jan 2026 14:02:47 +0100 Subject: [PATCH 05/38] Adagio: Enable site capability (#4320) --- src/main/resources/static/bidder-params/adagio.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/static/bidder-params/adagio.json b/src/main/resources/static/bidder-params/adagio.json index d0cbf17d8fc..af6a6bc4465 100644 --- a/src/main/resources/static/bidder-params/adagio.json +++ b/src/main/resources/static/bidder-params/adagio.json @@ -10,7 +10,7 @@ }, "placement": { "type": "string", - "description": "Refers to the placement of an adunit in a page. Must not contain any information about the type of device." + "description": "Refers to the placement of an adunit in a page. See Adagio recommended values: https://prebid.org/dev-docs/bidders/adagio.html#recommended-placement-param-values." }, "site": { "type": "string", From 7ff2b106c783e4b4ff5c0a7e629a1b3c8657e2ff Mon Sep 17 00:00:00 2001 From: przemkaczmarek <167743744+przemkaczmarek@users.noreply.github.com> Date: Mon, 12 Jan 2026 14:02:55 +0100 Subject: [PATCH 06/38] Adagio: Remove hardcoded seat name (#4319) --- .../java/org/prebid/server/bidder/adagio/AdagioBidder.java | 5 ++--- .../org/prebid/server/bidder/adagio/AdagioBidderTest.java | 7 +++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/adagio/AdagioBidder.java b/src/main/java/org/prebid/server/bidder/adagio/AdagioBidder.java index 460d6ac2e4e..a211f6267b4 100644 --- a/src/main/java/org/prebid/server/bidder/adagio/AdagioBidder.java +++ b/src/main/java/org/prebid/server/bidder/adagio/AdagioBidder.java @@ -59,12 +59,12 @@ private List extractBids(BidResponse bidResponse, List e .filter(Objects::nonNull) .flatMap(seatBid -> seatBid.getBid().stream() .filter(Objects::nonNull) - .map(bid -> makeBid(bid, bidResponse.getCur(), seatBid.getSeat(), errors))) + .map(bid -> makeBid(bid, bidResponse.getCur(), errors))) .filter(Objects::nonNull) .toList(); } - private BidderBid makeBid(Bid bid, String currency, String seat, List errors) { + private BidderBid makeBid(Bid bid, String currency, List errors) { try { final ExtBidPrebid extBidPrebid = parseBidExt(bid); return BidderBid.builder() @@ -72,7 +72,6 @@ private BidderBid makeBid(Bid bid, String currency, String seat, List Date: Mon, 12 Jan 2026 14:15:34 +0100 Subject: [PATCH 07/38] New adapter: Rename adoppler bidder adapter to elementaltv add adoppler as alias (#4326) --- .../model/AdopplerResponseAdsExt.java | 9 ---- .../adoppler/model/AdopplerResponseExt.java | 9 ---- .../model/AdopplerResponseVideoAdsExt.java | 9 ---- .../ElementalTVBidder.java} | 50 ++++++++----------- .../model/ElementalTVResponseAdsExt.java | 9 ++++ .../model/ElementalTVResponseExt.java | 9 ++++ .../model/ElementalTVResponseVideoAdsExt.java | 9 ++++ .../ext/request/adoppler/ExtImpAdoppler.java | 10 ---- .../elementaltv/ExtImpElementalTV.java | 9 ++++ ...ion.java => ElementalTVConfiguration.java} | 22 ++++---- .../resources/bidder-config/adoppler.yaml | 13 ----- .../resources/bidder-config/elementaltv.yaml | 18 +++++++ .../{adoppler.json => elementaltv.json} | 8 +-- .../ElementalTVBidderTest.java} | 30 +++++------ ...AdopplerTest.java => ElementalTVTest.java} | 17 ++++--- .../test-auction-elementaltv-request.json} | 5 +- .../test-auction-elementaltv-response.json} | 6 +-- .../test-elementaltv-bid-request-1.json} | 7 +-- .../test-elementaltv-bid-response-1.json} | 0 .../server/it/test-application.properties | 4 +- 20 files changed, 121 insertions(+), 132 deletions(-) delete mode 100644 src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseAdsExt.java delete mode 100644 src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseExt.java delete mode 100644 src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseVideoAdsExt.java rename src/main/java/org/prebid/server/bidder/{adoppler/AdopplerBidder.java => elementaltv/ElementalTVBidder.java} (77%) create mode 100644 src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseAdsExt.java create mode 100644 src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseExt.java create mode 100644 src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseVideoAdsExt.java delete mode 100644 src/main/java/org/prebid/server/proto/openrtb/ext/request/adoppler/ExtImpAdoppler.java create mode 100644 src/main/java/org/prebid/server/proto/openrtb/ext/request/elementaltv/ExtImpElementalTV.java rename src/main/java/org/prebid/server/spring/config/bidder/{AdopplerConfiguration.java => ElementalTVConfiguration.java} (58%) delete mode 100644 src/main/resources/bidder-config/adoppler.yaml create mode 100644 src/main/resources/bidder-config/elementaltv.yaml rename src/main/resources/static/bidder-params/{adoppler.json => elementaltv.json} (66%) rename src/test/java/org/prebid/server/bidder/{adoppler/AdopplerBidderTest.java => elementaltv/ElementalTVBidderTest.java} (87%) rename src/test/java/org/prebid/server/it/{AdopplerTest.java => ElementalTVTest.java} (51%) rename src/test/resources/org/prebid/server/it/openrtb2/{adoppler/test-auction-adoppler-request.json => elementaltv/test-auction-elementaltv-request.json} (73%) rename src/test/resources/org/prebid/server/it/openrtb2/{adoppler/test-auction-adoppler-response.json => elementaltv/test-auction-elementaltv-response.json} (86%) rename src/test/resources/org/prebid/server/it/openrtb2/{adoppler/test-adoppler-bid-request-1.json => elementaltv/test-elementaltv-bid-request-1.json} (89%) rename src/test/resources/org/prebid/server/it/openrtb2/{adoppler/test-adoppler-bid-response-1.json => elementaltv/test-elementaltv-bid-response-1.json} (100%) diff --git a/src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseAdsExt.java b/src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseAdsExt.java deleted file mode 100644 index d773617f068..00000000000 --- a/src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseAdsExt.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.prebid.server.bidder.adoppler.model; - -import lombok.Value; - -@Value(staticConstructor = "of") -public class AdopplerResponseAdsExt { - - AdopplerResponseVideoAdsExt video; -} diff --git a/src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseExt.java b/src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseExt.java deleted file mode 100644 index 090ad5faaed..00000000000 --- a/src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseExt.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.prebid.server.bidder.adoppler.model; - -import lombok.Value; - -@Value(staticConstructor = "of") -public class AdopplerResponseExt { - - AdopplerResponseAdsExt ads; -} diff --git a/src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseVideoAdsExt.java b/src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseVideoAdsExt.java deleted file mode 100644 index db64e1447e4..00000000000 --- a/src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseVideoAdsExt.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.prebid.server.bidder.adoppler.model; - -import lombok.Value; - -@Value(staticConstructor = "of") -public class AdopplerResponseVideoAdsExt { - - Integer duration; -} diff --git a/src/main/java/org/prebid/server/bidder/adoppler/AdopplerBidder.java b/src/main/java/org/prebid/server/bidder/elementaltv/ElementalTVBidder.java similarity index 77% rename from src/main/java/org/prebid/server/bidder/adoppler/AdopplerBidder.java rename to src/main/java/org/prebid/server/bidder/elementaltv/ElementalTVBidder.java index 761a2e21bc4..17cd2950b7a 100644 --- a/src/main/java/org/prebid/server/bidder/adoppler/AdopplerBidder.java +++ b/src/main/java/org/prebid/server/bidder/elementaltv/ElementalTVBidder.java @@ -1,4 +1,4 @@ -package org.prebid.server.bidder.adoppler; +package org.prebid.server.bidder.elementaltv; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; @@ -12,9 +12,9 @@ import io.vertx.core.http.HttpMethod; import org.apache.commons.lang3.StringUtils; import org.prebid.server.bidder.Bidder; -import org.prebid.server.bidder.adoppler.model.AdopplerResponseAdsExt; -import org.prebid.server.bidder.adoppler.model.AdopplerResponseExt; -import org.prebid.server.bidder.adoppler.model.AdopplerResponseVideoAdsExt; +import org.prebid.server.bidder.elementaltv.model.ElementalTVResponseAdsExt; +import org.prebid.server.bidder.elementaltv.model.ElementalTVResponseExt; +import org.prebid.server.bidder.elementaltv.model.ElementalTVResponseVideoAdsExt; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderCall; import org.prebid.server.bidder.model.BidderError; @@ -24,7 +24,7 @@ import org.prebid.server.json.DecodeException; import org.prebid.server.json.JacksonMapper; import org.prebid.server.proto.openrtb.ext.ExtPrebid; -import org.prebid.server.proto.openrtb.ext.request.adoppler.ExtImpAdoppler; +import org.prebid.server.proto.openrtb.ext.request.elementaltv.ExtImpElementalTV; import org.prebid.server.proto.openrtb.ext.response.BidType; import org.prebid.server.util.HttpUtil; @@ -36,17 +36,16 @@ import java.util.Map; import java.util.Objects; -public class AdopplerBidder implements Bidder { +public class ElementalTVBidder implements Bidder { - private static final TypeReference> ADOPPLER_EXT_TYPE_REFERENCE = + private static final TypeReference> ELEMENTALTV_EXT_TYPE_REFERENCE = new TypeReference<>() { }; - private static final String DEFAULT_CLIENT = "app"; private final String endpointTemplate; private final JacksonMapper mapper; - public AdopplerBidder(String endpointTemplate, JacksonMapper mapper) { + public ElementalTVBidder(String endpointTemplate, JacksonMapper mapper) { this.endpointTemplate = HttpUtil.validateUrl(Objects.requireNonNull(endpointTemplate)); this.mapper = Objects.requireNonNull(mapper); } @@ -58,7 +57,7 @@ public Result>> makeHttpRequests(BidRequest request for (Imp imp : request.getImp()) { try { - final ExtImpAdoppler validExtImp = parseAndValidateImpExt(imp); + final ExtImpElementalTV validExtImp = parseAndValidateImpExt(imp); final String updateRequestId = request.getId() + "-" + validExtImp.getAdunit(); final BidRequest updateRequest = request.toBuilder().id(updateRequestId).build(); final String url = resolveUrl(validExtImp); @@ -71,29 +70,22 @@ public Result>> makeHttpRequests(BidRequest request return Result.of(result, errors); } - private ExtImpAdoppler parseAndValidateImpExt(Imp imp) { - final ExtImpAdoppler extImpAdoppler; + private ExtImpElementalTV parseAndValidateImpExt(Imp imp) { + final ExtImpElementalTV extImpElementalTV; try { - extImpAdoppler = mapper.mapper().convertValue(imp.getExt(), ADOPPLER_EXT_TYPE_REFERENCE).getBidder(); + extImpElementalTV = mapper.mapper().convertValue(imp.getExt(), ELEMENTALTV_EXT_TYPE_REFERENCE).getBidder(); } catch (IllegalArgumentException e) { throw new PreBidException(e.getMessage()); } - if (StringUtils.isBlank(extImpAdoppler.getAdunit())) { - throw new PreBidException("adunit parameter is required for adoppler bidder"); + if (StringUtils.isBlank(extImpElementalTV.getAdunit())) { + throw new PreBidException("adunit parameter is required for elementaltv bidder"); } - return extImpAdoppler; + return extImpElementalTV; } - private String resolveUrl(ExtImpAdoppler extImp) { - final String client = extImp.getClient(); - + private String resolveUrl(ExtImpElementalTV extImp) { try { - final String accountIdMacro = StringUtils.isBlank(client) - ? DEFAULT_CLIENT - : HttpUtil.encodeUrl(client); - return endpointTemplate - .replace("{{AccountID}}", accountIdMacro) .replace("{{AdUnit}}", HttpUtil.encodeUrl(extImp.getAdunit())); } catch (Exception e) { throw new PreBidException(e.getMessage()); @@ -171,17 +163,17 @@ private BidderBid createBid(Bid bid, Map impTypes, String curre private void validateVideoBidExt(Bid bid) { final ObjectNode extNode = bid.getExt(); - final AdopplerResponseExt ext = extNode != null ? parseResponseExt(extNode) : null; - final AdopplerResponseAdsExt adsExt = ext != null ? ext.getAds() : null; - final AdopplerResponseVideoAdsExt videoAdsExt = adsExt != null ? adsExt.getVideo() : null; + final ElementalTVResponseExt ext = extNode != null ? parseResponseExt(extNode) : null; + final ElementalTVResponseAdsExt adsExt = ext != null ? ext.getAds() : null; + final ElementalTVResponseVideoAdsExt videoAdsExt = adsExt != null ? adsExt.getVideo() : null; if (videoAdsExt == null) { throw new PreBidException("$.seatbid.bid.ext.ads.video required"); } } - private AdopplerResponseExt parseResponseExt(ObjectNode ext) { + private ElementalTVResponseExt parseResponseExt(ObjectNode ext) { try { - return mapper.mapper().treeToValue(ext, AdopplerResponseExt.class); + return mapper.mapper().treeToValue(ext, ElementalTVResponseExt.class); } catch (JsonProcessingException e) { throw new PreBidException(e.getMessage(), e); } diff --git a/src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseAdsExt.java b/src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseAdsExt.java new file mode 100644 index 00000000000..1a23b70eaa9 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseAdsExt.java @@ -0,0 +1,9 @@ +package org.prebid.server.bidder.elementaltv.model; + +import lombok.Value; + +@Value(staticConstructor = "of") +public class ElementalTVResponseAdsExt { + + ElementalTVResponseVideoAdsExt video; +} diff --git a/src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseExt.java b/src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseExt.java new file mode 100644 index 00000000000..935d32e01b6 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseExt.java @@ -0,0 +1,9 @@ +package org.prebid.server.bidder.elementaltv.model; + +import lombok.Value; + +@Value(staticConstructor = "of") +public class ElementalTVResponseExt { + + ElementalTVResponseAdsExt ads; +} diff --git a/src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseVideoAdsExt.java b/src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseVideoAdsExt.java new file mode 100644 index 00000000000..76e3457b453 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseVideoAdsExt.java @@ -0,0 +1,9 @@ +package org.prebid.server.bidder.elementaltv.model; + +import lombok.Value; + +@Value(staticConstructor = "of") +public class ElementalTVResponseVideoAdsExt { + + Integer duration; +} diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/adoppler/ExtImpAdoppler.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/adoppler/ExtImpAdoppler.java deleted file mode 100644 index a727c59b34b..00000000000 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/adoppler/ExtImpAdoppler.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.prebid.server.proto.openrtb.ext.request.adoppler; - -import lombok.Value; - -@Value(staticConstructor = "of") -public class ExtImpAdoppler { - - String adunit; - String client; -} diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/elementaltv/ExtImpElementalTV.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/elementaltv/ExtImpElementalTV.java new file mode 100644 index 00000000000..bdd594996b4 --- /dev/null +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/elementaltv/ExtImpElementalTV.java @@ -0,0 +1,9 @@ +package org.prebid.server.proto.openrtb.ext.request.elementaltv; + +import lombok.Value; + +@Value(staticConstructor = "of") +public class ExtImpElementalTV { + + String adunit; +} diff --git a/src/main/java/org/prebid/server/spring/config/bidder/AdopplerConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/ElementalTVConfiguration.java similarity index 58% rename from src/main/java/org/prebid/server/spring/config/bidder/AdopplerConfiguration.java rename to src/main/java/org/prebid/server/spring/config/bidder/ElementalTVConfiguration.java index 89163bceeab..8cece283dd5 100644 --- a/src/main/java/org/prebid/server/spring/config/bidder/AdopplerConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/bidder/ElementalTVConfiguration.java @@ -1,7 +1,7 @@ package org.prebid.server.spring.config.bidder; import org.prebid.server.bidder.BidderDeps; -import org.prebid.server.bidder.adoppler.AdopplerBidder; +import org.prebid.server.bidder.elementaltv.ElementalTVBidder; import org.prebid.server.json.JacksonMapper; import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties; import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler; @@ -16,26 +16,26 @@ import jakarta.validation.constraints.NotBlank; @Configuration -@PropertySource(value = "classpath:/bidder-config/adoppler.yaml", factory = YamlPropertySourceFactory.class) -public class AdopplerConfiguration { +@PropertySource(value = "classpath:/bidder-config/elementaltv.yaml", factory = YamlPropertySourceFactory.class) +public class ElementalTVConfiguration { - private static final String BIDDER_NAME = "adoppler"; + private static final String BIDDER_NAME = "elementaltv"; - @Bean("adopplerConfigurationProperties") - @ConfigurationProperties("adapters.adoppler") + @Bean("elementalTVConfigurationProperties") + @ConfigurationProperties("adapters.elementaltv") BidderConfigurationProperties configurationProperties() { return new BidderConfigurationProperties(); } @Bean - BidderDeps adopplerBidderDeps(BidderConfigurationProperties adopplerConfigurationProperties, - @NotBlank @Value("${external-url}") String externalUrl, - JacksonMapper mapper) { + BidderDeps elementaltvBidderDeps(BidderConfigurationProperties elementalTVConfigurationProperties, + @NotBlank @Value("${external-url}") String externalUrl, + JacksonMapper mapper) { return BidderDepsAssembler.forBidder(BIDDER_NAME) - .withConfig(adopplerConfigurationProperties) + .withConfig(elementalTVConfigurationProperties) .usersyncerCreator(UsersyncerCreator.create(externalUrl)) - .bidderCreator(config -> new AdopplerBidder(config.getEndpoint(), mapper)) + .bidderCreator(config -> new ElementalTVBidder(config.getEndpoint(), mapper)) .assemble(); } } diff --git a/src/main/resources/bidder-config/adoppler.yaml b/src/main/resources/bidder-config/adoppler.yaml deleted file mode 100644 index 1f66b131d83..00000000000 --- a/src/main/resources/bidder-config/adoppler.yaml +++ /dev/null @@ -1,13 +0,0 @@ -adapters: - adoppler: - endpoint: http://{{AccountID}}.trustedmarketplace.io/ads/processHeaderBid/{{AdUnit}} - meta-info: - maintainer-email: info@adoppler.com - app-media-types: - - banner - - video - site-media-types: - - banner - - video - supported-vendors: - vendor-id: 0 diff --git a/src/main/resources/bidder-config/elementaltv.yaml b/src/main/resources/bidder-config/elementaltv.yaml new file mode 100644 index 00000000000..198b887ea4b --- /dev/null +++ b/src/main/resources/bidder-config/elementaltv.yaml @@ -0,0 +1,18 @@ +adapters: + elementaltv: + endpoint: https://pbs.elementaltv.io/ads/processHeaderBid/{{AdUnit}} + ortb-version: "2.6" + aliases: + adoppler: ~ + meta-info: + maintainer-email: support@elementaltv.com + app-media-types: + - banner + - video + - native + site-media-types: + - banner + - video + - native + supported-vendors: + vendor-id: 0 diff --git a/src/main/resources/static/bidder-params/adoppler.json b/src/main/resources/static/bidder-params/elementaltv.json similarity index 66% rename from src/main/resources/static/bidder-params/adoppler.json rename to src/main/resources/static/bidder-params/elementaltv.json index eaa4e6df80e..f80e6c4f0d7 100644 --- a/src/main/resources/static/bidder-params/adoppler.json +++ b/src/main/resources/static/bidder-params/elementaltv.json @@ -1,16 +1,12 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Adoppler Adapter Params", - "description": "A schema which validates params accepted by the Adoppler adapter", + "title": "ElementalTV Adapter Params", + "description": "A schema which validates params accepted by the ElementalTV adapter", "type": "object", "properties": { "adunit": { "type": "string", "description": "AdUnit to bid against to." - }, - "client": { - "type": "string", - "description": "Client name." } }, "required": ["adunit"] diff --git a/src/test/java/org/prebid/server/bidder/adoppler/AdopplerBidderTest.java b/src/test/java/org/prebid/server/bidder/elementaltv/ElementalTVBidderTest.java similarity index 87% rename from src/test/java/org/prebid/server/bidder/adoppler/AdopplerBidderTest.java rename to src/test/java/org/prebid/server/bidder/elementaltv/ElementalTVBidderTest.java index 2004ce97118..b06bf5f4fd5 100644 --- a/src/test/java/org/prebid/server/bidder/adoppler/AdopplerBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/elementaltv/ElementalTVBidderTest.java @@ -1,4 +1,4 @@ -package org.prebid.server.bidder.adoppler; +package org.prebid.server.bidder.elementaltv; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -12,8 +12,8 @@ import io.netty.handler.codec.http.HttpHeaderValues; import org.junit.jupiter.api.Test; import org.prebid.server.VertxTest; -import org.prebid.server.bidder.adoppler.model.AdopplerResponseAdsExt; -import org.prebid.server.bidder.adoppler.model.AdopplerResponseExt; +import org.prebid.server.bidder.elementaltv.model.ElementalTVResponseAdsExt; +import org.prebid.server.bidder.elementaltv.model.ElementalTVResponseExt; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderCall; import org.prebid.server.bidder.model.BidderError; @@ -21,7 +21,7 @@ import org.prebid.server.bidder.model.HttpResponse; import org.prebid.server.bidder.model.Result; import org.prebid.server.proto.openrtb.ext.ExtPrebid; -import org.prebid.server.proto.openrtb.ext.request.adoppler.ExtImpAdoppler; +import org.prebid.server.proto.openrtb.ext.request.elementaltv.ExtImpElementalTV; import org.prebid.server.util.HttpUtil; import java.util.Collections; @@ -35,15 +35,15 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.tuple; -public class AdopplerBidderTest extends VertxTest { +public class ElementalTVBidderTest extends VertxTest { - private static final String ENDPOINT_URL = "http://{{AccountID}}.test.com/some/path/{{AdUnit}}"; + private static final String ENDPOINT_URL = "https://pbs.test.com/some/path/{{AdUnit}}"; - private final AdopplerBidder target = new AdopplerBidder(ENDPOINT_URL, jacksonMapper); + private final ElementalTVBidder target = new ElementalTVBidder(ENDPOINT_URL, jacksonMapper); @Test public void creationShouldFailOnInvalidEndpointUrl() { - assertThatIllegalArgumentException().isThrownBy(() -> new AdopplerBidder("invalid_url", jacksonMapper)); + assertThatIllegalArgumentException().isThrownBy(() -> new ElementalTVBidder("invalid_url", jacksonMapper)); } @Test @@ -51,13 +51,13 @@ public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { // given final BidRequest bidRequest = givenBidRequest( impBuilder -> impBuilder - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpAdoppler.of(null, null))))); + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpElementalTV.of(null))))); // when final Result>> result = target.makeHttpRequests(bidRequest); // then assertThat(result.getErrors()) - .containsExactly(BidderError.badInput("adunit parameter is required for adoppler bidder")); + .containsExactly(BidderError.badInput("adunit parameter is required for elementaltv bidder")); } @Test @@ -71,7 +71,7 @@ public void makeHttpRequestsShouldCreateCorrectURL() { // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).hasSize(1); - assertThat(result.getValue().getFirst().getUri()).isEqualTo("http://clientId.test.com/some/path/adUnit"); + assertThat(result.getValue().getFirst().getUri()).isEqualTo("https://pbs.test.com/some/path/adUnit"); } @Test @@ -79,7 +79,7 @@ public void makeHttpRequestsShouldCreateUrlWithDefaultAppParamIfClientIsMissing( // given final BidRequest bidRequest = givenBidRequest( impBuilder -> impBuilder - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpAdoppler.of("adUnit", ""))))); + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpElementalTV.of("adUnit"))))); // when final Result>> result = target.makeHttpRequests(bidRequest); @@ -87,7 +87,7 @@ public void makeHttpRequestsShouldCreateUrlWithDefaultAppParamIfClientIsMissing( // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).hasSize(1); - assertThat(result.getValue().getFirst().getUri()).isEqualTo("http://app.test.com/some/path/adUnit"); + assertThat(result.getValue().getFirst().getUri()).isEqualTo("https://pbs.test.com/some/path/adUnit"); } @Test @@ -133,7 +133,7 @@ public void makeBidsShouldReturnErrorIfExtEmpty() throws JsonProcessingException // given final Imp imp = Imp.builder().id("impId").video(Video.builder().build()).build(); final BidRequest bidRequest = BidRequest.builder().imp(Collections.singletonList(imp)).build(); - final ObjectNode ext = mapper.valueToTree(AdopplerResponseExt.of(AdopplerResponseAdsExt.of(null))); + final ObjectNode ext = mapper.valueToTree(ElementalTVResponseExt.of(ElementalTVResponseAdsExt.of(null))); final BidderCall httpCall = givenHttpCall(bidRequest, mapper.writeValueAsString( givenBidResponse(bidBuilder -> bidBuilder .id("321") @@ -166,7 +166,7 @@ private static Imp givenImp(Function impCustomiz return impCustomizer.apply(Imp.builder() .id("123") .banner(Banner.builder().id("banner_id").build()) - .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpAdoppler.of("adUnit", "clientId"))))) + .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpElementalTV.of("adUnit"))))) .build(); } diff --git a/src/test/java/org/prebid/server/it/AdopplerTest.java b/src/test/java/org/prebid/server/it/ElementalTVTest.java similarity index 51% rename from src/test/java/org/prebid/server/it/AdopplerTest.java rename to src/test/java/org/prebid/server/it/ElementalTVTest.java index 6e3e9960502..106a800edb4 100644 --- a/src/test/java/org/prebid/server/it/AdopplerTest.java +++ b/src/test/java/org/prebid/server/it/ElementalTVTest.java @@ -13,21 +13,22 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static java.util.Collections.singletonList; -public class AdopplerTest extends IntegrationTest { +public class ElementalTVTest extends IntegrationTest { @Test - public void openrtb2AuctionShouldRespondWithBidsFromAdoppler() throws IOException, JSONException { + public void openrtb2AuctionShouldRespondWithBidsFromElementalTV() throws IOException, JSONException { // given - WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/adoppler-exchange")) - .withRequestBody(equalToJson(jsonFrom("openrtb2/adoppler/test-adoppler-bid-request-1.json"))) - .willReturn(aResponse().withBody(jsonFrom("openrtb2/adoppler/test-adoppler-bid-response-1.json")))); + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/elementaltv-exchange")) + .withRequestBody(equalToJson(jsonFrom("openrtb2/elementaltv/test-elementaltv-bid-request-1.json"))) + .willReturn(aResponse() + .withBody(jsonFrom("openrtb2/elementaltv/test-elementaltv-bid-response-1.json")))); // when - final Response response = responseFor("openrtb2/adoppler/test-auction-adoppler-request.json", + final Response response = responseFor("openrtb2/elementaltv/test-auction-elementaltv-request.json", Endpoint.openrtb2_auction); // then - assertJsonEquals("openrtb2/adoppler/test-auction-adoppler-response.json", response, - singletonList("adoppler")); + assertJsonEquals("openrtb2/elementaltv/test-auction-elementaltv-response.json", response, + singletonList("elementaltv")); } } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adoppler/test-auction-adoppler-request.json b/src/test/resources/org/prebid/server/it/openrtb2/elementaltv/test-auction-elementaltv-request.json similarity index 73% rename from src/test/resources/org/prebid/server/it/openrtb2/adoppler/test-auction-adoppler-request.json rename to src/test/resources/org/prebid/server/it/openrtb2/elementaltv/test-auction-elementaltv-request.json index 671adea62e9..c9c065751d4 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adoppler/test-auction-adoppler-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/elementaltv/test-auction-elementaltv-request.json @@ -8,9 +8,8 @@ "h": 200 }, "ext": { - "adoppler": { - "adunit": "adunit", - "client": "testClient" + "elementaltv": { + "adunit": "adunit" } } } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adoppler/test-auction-adoppler-response.json b/src/test/resources/org/prebid/server/it/openrtb2/elementaltv/test-auction-elementaltv-response.json similarity index 86% rename from src/test/resources/org/prebid/server/it/openrtb2/adoppler/test-auction-adoppler-response.json rename to src/test/resources/org/prebid/server/it/openrtb2/elementaltv/test-auction-elementaltv-response.json index cbfeaa10b25..5045d4c4a02 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adoppler/test-auction-adoppler-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/elementaltv/test-auction-elementaltv-response.json @@ -20,7 +20,7 @@ "prebid": { "type": "banner", "meta": { - "adaptercode": "adoppler" + "adaptercode": "elementaltv" } }, "origbidcpm": 3.33, @@ -32,14 +32,14 @@ } } ], - "seat": "adoppler", + "seat": "elementaltv", "group": 0 } ], "cur": "USD", "ext": { "responsetimemillis": { - "adoppler": "{{ adoppler.response_time_ms }}" + "elementaltv": "{{ elementaltv.response_time_ms }}" }, "prebid": { "auctiontimestamp": 0 diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adoppler/test-adoppler-bid-request-1.json b/src/test/resources/org/prebid/server/it/openrtb2/elementaltv/test-elementaltv-bid-request-1.json similarity index 89% rename from src/test/resources/org/prebid/server/it/openrtb2/adoppler/test-adoppler-bid-request-1.json rename to src/test/resources/org/prebid/server/it/openrtb2/elementaltv/test-elementaltv-bid-request-1.json index 85ca49d2c95..c21c21f4f2f 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/adoppler/test-adoppler-bid-request-1.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/elementaltv/test-elementaltv-bid-request-1.json @@ -11,8 +11,7 @@ "ext": { "tid": "${json-unit.any-string}", "bidder": { - "adunit": "adunit", - "client": "testClient" + "adunit": "adunit" } } } @@ -40,9 +39,7 @@ "USD" ], "regs": { - "ext": { - "gdpr": 0 - } + "gdpr" : 0 }, "ext": { "prebid": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adoppler/test-adoppler-bid-response-1.json b/src/test/resources/org/prebid/server/it/openrtb2/elementaltv/test-elementaltv-bid-response-1.json similarity index 100% rename from src/test/resources/org/prebid/server/it/openrtb2/adoppler/test-adoppler-bid-response-1.json rename to src/test/resources/org/prebid/server/it/openrtb2/elementaltv/test-elementaltv-bid-response-1.json diff --git a/src/test/resources/org/prebid/server/it/test-application.properties b/src/test/resources/org/prebid/server/it/test-application.properties index 1a073dcda06..2a3ffe37083 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -64,8 +64,8 @@ adapters.adnuntius.endpoint=http://localhost:8090/adnuntius-exchange adapters.adnuntius.eu-endpoint=http://localhost:8090/adnuntius-exchange-eu adapters.adocean.enabled=true adapters.adocean.endpoint=http://localhost:8090/adocean-exchange -adapters.adoppler.enabled=true -adapters.adoppler.endpoint=http://localhost:8090/adoppler-exchange +adapters.elementaltv.enabled=true +adapters.elementaltv.endpoint=http://localhost:8090/elementaltv-exchange adapters.adpone.enabled=true adapters.adpone.endpoint=http://localhost:8090/adpone-exchange adapters.adot.enabled=true From 18899b89f4c6b67a0f3e23b42f7b9454f2014fff Mon Sep 17 00:00:00 2001 From: Philip Watson Date: Tue, 13 Jan 2026 02:44:32 +1300 Subject: [PATCH 08/38] StroeerCore: Use bid.ext as-is from the response (#4317) --- .../bidder/stroeercore/StroeerCoreBidder.java | 18 +++-- .../stroeercore/model/StroeerCoreBid.java | 6 ++ .../stroeercore/StroeerCoreBidderTest.java | 66 ++++++++++++++++++- 3 files changed, 82 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/stroeercore/StroeerCoreBidder.java b/src/main/java/org/prebid/server/bidder/stroeercore/StroeerCoreBidder.java index cff5866d027..fea452f059d 100644 --- a/src/main/java/org/prebid/server/bidder/stroeercore/StroeerCoreBidder.java +++ b/src/main/java/org/prebid/server/bidder/stroeercore/StroeerCoreBidder.java @@ -106,10 +106,6 @@ private BidderBid toBidderBid(StroeerCoreBid stroeercoreBid, List e return null; } - final ObjectNode bidExt = stroeercoreBid.getDsa() != null - ? mapper.mapper().createObjectNode().set("dsa", stroeercoreBid.getDsa()) - : null; - return BidderBid.of( Bid.builder() .id(stroeercoreBid.getId()) @@ -121,12 +117,24 @@ private BidderBid toBidderBid(StroeerCoreBid stroeercoreBid, List e .crid(stroeercoreBid.getCreativeId()) .adomain(stroeercoreBid.getAdomain()) .mtype(bidType.ordinal() + 1) - .ext(bidExt) + .ext(getBidExt(stroeercoreBid)) .build(), bidType, BIDDER_CURRENCY); } + private ObjectNode getBidExt(StroeerCoreBid stroeercoreBid) { + final ObjectNode dsa = stroeercoreBid.getDsa(); + ObjectNode ext = stroeercoreBid.getExt(); + if (dsa == null) { + return ext; + } + if (ext == null) { + ext = mapper.mapper().createObjectNode(); + } + return ext.set("dsa", dsa); + } + private static BidType getBidType(String mtype) { return switch (mtype) { case "banner" -> BidType.banner; diff --git a/src/main/java/org/prebid/server/bidder/stroeercore/model/StroeerCoreBid.java b/src/main/java/org/prebid/server/bidder/stroeercore/model/StroeerCoreBid.java index 2c665a27ce5..980db6d1891 100644 --- a/src/main/java/org/prebid/server/bidder/stroeercore/model/StroeerCoreBid.java +++ b/src/main/java/org/prebid/server/bidder/stroeercore/model/StroeerCoreBid.java @@ -29,9 +29,15 @@ public class StroeerCoreBid { @JsonProperty("crid") String creativeId; + /** + * @deprecated The dsa will move to the bid's ext. + */ + @Deprecated(forRemoval = true) ObjectNode dsa; String mtype; List adomain; + + ObjectNode ext; } diff --git a/src/test/java/org/prebid/server/bidder/stroeercore/StroeerCoreBidderTest.java b/src/test/java/org/prebid/server/bidder/stroeercore/StroeerCoreBidderTest.java index aa918224d29..ecee159f8bd 100644 --- a/src/test/java/org/prebid/server/bidder/stroeercore/StroeerCoreBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/stroeercore/StroeerCoreBidderTest.java @@ -152,7 +152,9 @@ public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { @Test public void makeBidsShouldReturnExpectedBannerBid() throws JsonProcessingException { // given - final ObjectNode dsaResponse = createDsaResponse(); + final ObjectNode bidExt = mapper.createObjectNode(); + bidExt.put("something", "else"); + bidExt.set("dsa", createDsaResponse()); final StroeerCoreBid bannerBid = StroeerCoreBid.builder() .id("1") @@ -163,7 +165,7 @@ public void makeBidsShouldReturnExpectedBannerBid() throws JsonProcessingExcepti .width(300) .height(600) .mtype("banner") - .dsa(dsaResponse.deepCopy()) + .ext(bidExt.deepCopy()) .adomain(List.of("domain1.com", "domain2.com")) .build(); @@ -184,13 +186,71 @@ public void makeBidsShouldReturnExpectedBannerBid() throws JsonProcessingExcepti .h(600) .adomain(List.of("domain1.com", "domain2.com")) .mtype(1) - .ext(mapper.createObjectNode().set("dsa", dsaResponse)) + .ext(bidExt) .build(); assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).containsOnly(BidderBid.of(expectedBannerBid, BidType.banner, "EUR")); } + @Test + public void makeBidsShouldSupportDeprecatedDsaPath() throws JsonProcessingException { + // given + final ObjectNode dsa = createDsaResponse(); + + final StroeerCoreBid.StroeerCoreBidBuilder stroeerBidBuilder = StroeerCoreBid.builder() + .id("1") + .bidId("banner-imp-id") + .adMarkup("
") + .cpm(BigDecimal.valueOf(0.3)) + .creativeId("foo") + .width(300) + .height(600) + .mtype("banner") + .adomain(List.of("domain1.com", "domain2.com")) + .dsa(dsa.deepCopy()); + + final StroeerCoreBid bannerBidWithoutExt = stroeerBidBuilder + .ext(null) + .build(); + + final StroeerCoreBid bannerBidWithExt = stroeerBidBuilder + .ext(mapper.createObjectNode().put("xyz", true)) + .build(); + + final StroeerCoreBidResponse response = StroeerCoreBidResponse.of( + List.of(bannerBidWithoutExt, bannerBidWithExt)); + final BidderCall httpCall = createHttpCall(response); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + final Bid.BidBuilder expectedBannerBidBuilder = Bid.builder() + .id("1") + .impid("banner-imp-id") + .adm("
") + .price(BigDecimal.valueOf(0.3)) + .crid("foo") + .w(300) + .h(600) + .adomain(List.of("domain1.com", "domain2.com")) + .mtype(1); + + final Bid expectedBid1 = expectedBannerBidBuilder + .ext(mapper.createObjectNode().set("dsa", dsa)) + .build(); + + final Bid expectedBid2 = expectedBannerBidBuilder + .ext(mapper.createObjectNode().put("xyz", true).set("dsa", dsa)) + .build(); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsOnly( + BidderBid.of(expectedBid1, BidType.banner, "EUR"), + BidderBid.of(expectedBid2, BidType.banner, "EUR")); + } + @Test public void makeBidsShouldReturnExpectedBidderBids() throws JsonProcessingException { // given From 4cdb2d1f32da776be3925bb82661a59592e1e950 Mon Sep 17 00:00:00 2001 From: Aleksandar Crepuljarevic Date: Tue, 13 Jan 2026 12:26:06 +0100 Subject: [PATCH 09/38] Ogury: Allow inventory mapping for in-app traffic. (#4321) --- .../server/bidder/ogury/OguryBidder.java | 20 +++++++--- .../server/bidder/ogury/OguryBidderTest.java | 40 +++++++++++++++---- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/ogury/OguryBidder.java b/src/main/java/org/prebid/server/bidder/ogury/OguryBidder.java index 3626f88b93b..4d14198e230 100644 --- a/src/main/java/org/prebid/server/bidder/ogury/OguryBidder.java +++ b/src/main/java/org/prebid/server/bidder/ogury/OguryBidder.java @@ -1,6 +1,7 @@ package org.prebid.server.bidder.ogury; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.App; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Device; import com.iab.openrtb.request.Imp; @@ -71,14 +72,10 @@ public Result>> makeHttpRequests(BidRequest bidRequ } if (CollectionUtils.isEmpty(impsWithOguryParams)) { - if (bidRequest.getApp() != null) { - errors.add(BidderError.badInput("Invalid request. assetKey/adUnitId required")); - return Result.withErrors(errors); - } - // for "site" request we can serve ads with just publisher.id + // we can serve ads with just publisher.id if (!hasPublisherId(bidRequest)) { errors.add(BidderError.badInput( - "Invalid request. assetKey/adUnitId or request.site.publisher.id required")); + "Invalid request. assetKey/adUnitId or request.site/app.publisher.id required")); return Result.withErrors(errors); } } @@ -145,12 +142,23 @@ private boolean hasOguryParams(Imp imp) { } private boolean hasPublisherId(BidRequest request) { + return hasSitePublisherId(request) || hasAppPublisherId(request); + } + + private boolean hasSitePublisherId(BidRequest request) { return Optional.ofNullable(request.getSite()) .map(Site::getPublisher) .map(Publisher::getId) .isPresent(); } + private boolean hasAppPublisherId(BidRequest request) { + return Optional.ofNullable(request.getApp()) + .map(App::getPublisher) + .map(Publisher::getId) + .isPresent(); + } + private MultiMap resolveHeaders(Device device) { final MultiMap headers = HttpUtil.headers(); diff --git a/src/test/java/org/prebid/server/bidder/ogury/OguryBidderTest.java b/src/test/java/org/prebid/server/bidder/ogury/OguryBidderTest.java index d5426ab415d..d8f26170ccb 100644 --- a/src/test/java/org/prebid/server/bidder/ogury/OguryBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/ogury/OguryBidderTest.java @@ -124,10 +124,9 @@ public void makeHttpRequestsShouldReturnErrorWhenRequestDoesNotHaveOguryKeys() { // when final Result>> result = target.makeHttpRequests(bidrequest); - // then assertThat(result.getErrors()).containsExactly( - BidderError.badInput("Invalid request. assetKey/adUnitId or request.site.publisher.id required")); + BidderError.badInput("Invalid request. assetKey/adUnitId or request.site/app.publisher.id required")); } @Test @@ -189,7 +188,7 @@ public void makeHttpRequestsShouldNotSendImpsWhenHasNotPublisherIdAndImpsWithOgu assertThat(result.getValue()).isEmpty(); assertThat(result.getErrors()).containsExactly( - BidderError.badInput("Invalid request. assetKey/adUnitId or request.site.publisher.id required")); + BidderError.badInput("Invalid request. assetKey/adUnitId or request.site/app.publisher.id required")); } @Test @@ -214,7 +213,7 @@ public void makeHttpRequestsAppShouldSendOnlyImpsWithOguryParamsIfPresent() { } @Test - public void makeHttpRequestsAppShouldNotSendImpsWhenImpsWithOguryIsEmpty() { + public void makeHttpRequestsAppShouldSendAllImpsWhenHasPublisherIdAndImpsWithOguryIsEmpty() { // given final ObjectNode emptyImpExt = givenEmptyImpExt(); @@ -226,11 +225,33 @@ public void makeHttpRequestsAppShouldNotSendImpsWhenImpsWithOguryIsEmpty() { // when final Result>> result = target.makeHttpRequests(bidrequest); + // then + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .hasSize(2); + + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void makeHttpRequestsAppShouldNotSendImpsWhenHasNotPublisherIdAndImpsWithOguryIsEmpty() { + // given + final ObjectNode emptyImpExt = givenEmptyImpExt(); + + final BidRequest bidrequest = givenBidRequest( + bidRequest -> bidRequest.app(App.builder().bundle("app_bundle").build()), + givenImp(imp -> imp.id("id1").ext(emptyImpExt)), + givenImp(imp -> imp.id("id2").ext(emptyImpExt))); + + // when + final Result>> result = target.makeHttpRequests(bidrequest); + // then assertThat(result.getValue()).isEmpty(); assertThat(result.getErrors()).containsExactly( - BidderError.badInput("Invalid request. assetKey/adUnitId required")); + BidderError.badInput("Invalid request. assetKey/adUnitId or request.site/app.publisher.id required")); } @Test @@ -260,7 +281,7 @@ public void makeHttpRequestsShouldCleanImpExtWithoutLostExtraFields() { extWithOguryKeys.put("extra_field", "extra_value"); final BidRequest bidrequest = givenBidRequest( - identity(), + bidRequest -> bidRequest.site(givenSite()), givenImp(imp -> imp.id("id1").ext(extWithOguryKeys))); // when @@ -607,7 +628,12 @@ private Site givenSite() { } private App givenApp() { - return App.builder().bundle("app_bundle").build(); + return App.builder() + .bundle("app_bundle") + .publisher(Publisher.builder() + .id("publiser_id") + .build()) + .build(); } private ObjectNode givenEmptyImpExt() { From 25f7b4932fa7231a52ab45e3c3f9f598e82941c6 Mon Sep 17 00:00:00 2001 From: Piotr Jaworski <109736938+pjaworski-rtbh@users.noreply.github.com> Date: Tue, 13 Jan 2026 12:26:21 +0100 Subject: [PATCH 10/38] RTBHouse: Extract tagid from Imp fields (#4306) --- .../bidder/rtbhouse/RtbhouseBidder.java | 194 ++++---- .../bidder/rtbhouse/RtbhouseBidderTest.java | 417 ++++++++++++------ .../rtbhouse/test-rtbhouse-bid-request.json | 3 +- 3 files changed, 394 insertions(+), 220 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/rtbhouse/RtbhouseBidder.java b/src/main/java/org/prebid/server/bidder/rtbhouse/RtbhouseBidder.java index f2228778feb..8d45320cb9c 100644 --- a/src/main/java/org/prebid/server/bidder/rtbhouse/RtbhouseBidder.java +++ b/src/main/java/org/prebid/server/bidder/rtbhouse/RtbhouseBidder.java @@ -94,6 +94,110 @@ public Result>> makeHttpRequests(BidRequest bidRequ return Result.withValue(BidderUtil.defaultRequest(outgoingRequest, endpointUrl, mapper)); } + private ExtImpRtbhouse parseImpExt(Imp imp) { + try { + return mapper.mapper().convertValue(imp.getExt(), RTBHOUSE_EXT_TYPE_REFERENCE).getBidder(); + } catch (IllegalArgumentException e) { + throw new PreBidException(e.getMessage()); + } + } + + private Price resolveBidFloor(Imp imp, ExtImpRtbhouse impExt, BidRequest bidRequest) { + final List brCur = bidRequest.getCur(); + final Price initialBidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor()); + + final BigDecimal impExtBidFloor = impExt.getBidFloor(); + final String impExtCurrency = impExtBidFloor != null && brCur != null && !brCur.isEmpty() + ? brCur.getFirst() : null; + final Price impExtBidFloorPrice = Price.of(impExtCurrency, impExtBidFloor); + final Price resolvedPrice = initialBidFloorPrice.getValue() == null + ? impExtBidFloorPrice : initialBidFloorPrice; + + return BidderUtil.isValidPrice(resolvedPrice) + && !StringUtils.equalsIgnoreCase(resolvedPrice.getCurrency(), BIDDER_CURRENCY) + ? convertBidFloor(resolvedPrice, imp.getId(), bidRequest) + : resolvedPrice; + } + + private Price convertBidFloor(Price bidFloorPrice, String impId, BidRequest bidRequest) { + final String bidFloorCur = bidFloorPrice.getCurrency(); + try { + final BigDecimal convertedPrice = currencyConversionService + .convertCurrency(bidFloorPrice.getValue(), bidRequest, bidFloorCur, BIDDER_CURRENCY); + + return Price.of(BIDDER_CURRENCY, convertedPrice); + } catch (PreBidException e) { + throw new PreBidException(String.format( + "Unable to convert provided bid floor currency from %s to %s for imp `%s`", + bidFloorCur, BIDDER_CURRENCY, impId)); + } + } + + private static Imp modifyImp(Imp imp, Price bidFloorPrice) { + return imp.toBuilder() + .tagid(extractTagId(imp)) + .bidfloorcur(ObjectUtil.getIfNotNull(bidFloorPrice, Price::getCurrency)) + .bidfloor(ObjectUtil.getIfNotNull(bidFloorPrice, Price::getValue)) + .pmp(null) + .build(); + } + + private static String extractTagId(Imp imp) { + return Optional.ofNullable(imp.getTagid()) + .filter(StringUtils::isNotBlank) + .or(() -> extractGpid(imp)) + .or(() -> extractAdslot(imp)) + .or(() -> extractPbadslot(imp)) + .or(() -> Optional.ofNullable(imp.getId()) + .filter(StringUtils::isNotBlank)) + .orElse(null); + } + + private static Optional extractGpid(Imp imp) { + return Optional.ofNullable(imp.getExt()) + .map(ext -> ext.get("gpid")) + .map(JsonNode::textValue) + .filter(StringUtils::isNotBlank); + } + + private static Optional extractAdslot(Imp imp) { + return Optional.ofNullable(imp.getExt()) + .map(ext -> ext.get("data")) + .map(data -> data.get("adserver")) + .map(adserver -> adserver.get("adslot")) + .map(JsonNode::textValue) + .filter(StringUtils::isNotBlank); + } + + private static Optional extractPbadslot(Imp imp) { + return Optional.ofNullable(imp.getExt()) + .map(ext -> ext.get("data")) + .map(data -> data.get("pbadslot")) + .map(JsonNode::textValue) + .filter(StringUtils::isNotBlank); + } + + private Site modifySite(Site site, String publisherId) { + final ObjectNode prebidNode = mapper.mapper().createObjectNode(); + prebidNode.put("publisherId", publisherId); + + final ExtPublisher extPublisher = ExtPublisher.empty(); + extPublisher.addProperty("prebid", prebidNode); + + final Publisher publisher = Optional.ofNullable(site) + .map(Site::getPublisher) + .map(Publisher::toBuilder) + .orElseGet(Publisher::builder) + .ext(extPublisher) + .build(); + + return Optional.ofNullable(site) + .map(Site::toBuilder) + .orElseGet(Site::builder) + .publisher(publisher) + .build(); + } + @Override public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { try { @@ -144,23 +248,6 @@ private BidderBid resolveBidderBid(Bid bid, .build(); } - private String resolveNativeAdm(String adm, List bidderErrors) { - final JsonNode admNode; - try { - admNode = mapper.mapper().readTree(adm); - } catch (JsonProcessingException e) { - bidderErrors.add(BidderError.badServerResponse("Unable to parse native adm: %s".formatted(adm))); - return null; - } - - final JsonNode nativeNode = admNode.get("native"); - if (nativeNode != null) { - return nativeNode.toString(); - } - - return adm; - } - private static BidType getBidType(String impId, List imps) { for (Imp imp : imps) { if (imp.getId().equals(impId)) { @@ -176,51 +263,21 @@ private static BidType getBidType(String impId, List imps) { return BidType.banner; } - private ExtImpRtbhouse parseImpExt(Imp imp) { + private String resolveNativeAdm(String adm, List bidderErrors) { + final JsonNode admNode; try { - return mapper.mapper().convertValue(imp.getExt(), RTBHOUSE_EXT_TYPE_REFERENCE).getBidder(); - } catch (IllegalArgumentException e) { - throw new PreBidException(e.getMessage()); + admNode = mapper.mapper().readTree(adm); + } catch (JsonProcessingException e) { + bidderErrors.add(BidderError.badServerResponse("Unable to parse native adm: %s".formatted(adm))); + return null; } - } - private static Imp modifyImp(Imp imp, Price bidFloorPrice) { - return imp.toBuilder() - .bidfloorcur(ObjectUtil.getIfNotNull(bidFloorPrice, Price::getCurrency)) - .bidfloor(ObjectUtil.getIfNotNull(bidFloorPrice, Price::getValue)) - .pmp(null) - .build(); - } - - private Price resolveBidFloor(Imp imp, ExtImpRtbhouse impExt, BidRequest bidRequest) { - final List brCur = bidRequest.getCur(); - final Price initialBidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor()); - - final BigDecimal impExtBidFloor = impExt.getBidFloor(); - final String impExtCurrency = impExtBidFloor != null && brCur != null && !brCur.isEmpty() - ? brCur.getFirst() : null; - final Price impExtBidFloorPrice = Price.of(impExtCurrency, impExtBidFloor); - final Price resolvedPrice = initialBidFloorPrice.getValue() == null - ? impExtBidFloorPrice : initialBidFloorPrice; - - return BidderUtil.isValidPrice(resolvedPrice) - && !StringUtils.equalsIgnoreCase(resolvedPrice.getCurrency(), BIDDER_CURRENCY) - ? convertBidFloor(resolvedPrice, imp.getId(), bidRequest) - : resolvedPrice; - } - - private Price convertBidFloor(Price bidFloorPrice, String impId, BidRequest bidRequest) { - final String bidFloorCur = bidFloorPrice.getCurrency(); - try { - final BigDecimal convertedPrice = currencyConversionService - .convertCurrency(bidFloorPrice.getValue(), bidRequest, bidFloorCur, BIDDER_CURRENCY); - - return Price.of(BIDDER_CURRENCY, convertedPrice); - } catch (PreBidException e) { - throw new PreBidException(String.format( - "Unable to convert provided bid floor currency from %s to %s for imp `%s`", - bidFloorCur, BIDDER_CURRENCY, impId)); + final JsonNode nativeNode = admNode.get("native"); + if (nativeNode != null) { + return nativeNode.toString(); } + + return adm; } private static Bid resolveMacros(Bid bid) { @@ -232,25 +289,4 @@ private static Bid resolveMacros(Bid bid) { .adm(StringUtils.replace(bid.getAdm(), PRICE_MACRO, priceAsString)) .build(); } - - private Site modifySite(Site site, String publisherId) { - final ObjectNode prebidNode = mapper.mapper().createObjectNode(); - prebidNode.put("publisherId", publisherId); - - final ExtPublisher extPublisher = ExtPublisher.empty(); - extPublisher.addProperty("prebid", prebidNode); - - final Publisher publisher = Optional.ofNullable(site) - .map(Site::getPublisher) - .map(Publisher::toBuilder) - .orElseGet(Publisher::builder) - .ext(extPublisher) - .build(); - - return Optional.ofNullable(site) - .map(Site::toBuilder) - .orElseGet(Site::builder) - .publisher(publisher) - .build(); - } } diff --git a/src/test/java/org/prebid/server/bidder/rtbhouse/RtbhouseBidderTest.java b/src/test/java/org/prebid/server/bidder/rtbhouse/RtbhouseBidderTest.java index fe10dad45e3..7a4e483303c 100644 --- a/src/test/java/org/prebid/server/bidder/rtbhouse/RtbhouseBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/rtbhouse/RtbhouseBidderTest.java @@ -34,7 +34,9 @@ import java.math.BigDecimal; import java.util.List; +import java.util.Map; import java.util.function.Function; +import java.util.function.UnaryOperator; import static java.util.Collections.singletonList; import static java.util.function.Function.identity; @@ -70,96 +72,6 @@ public void creationShouldFailOnInvalidEndpointUrl() { jacksonMapper)); } - @Test - public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { - // given - final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.banner(null)); - final BidderCall httpCall = givenHttpCall(bidRequest, "invalid"); - - // when - final Result> result = target.makeBids(httpCall, bidRequest); - - // then - assertThat(result.getErrors()).hasSize(1); - assertThat(result.getErrors().getFirst().getMessage()).startsWith("Failed to decode: Unrecognized token"); - assertThat(result.getErrors().getFirst().getType()).isEqualTo(BidderError.Type.bad_server_response); - assertThat(result.getValue()).isEmpty(); - } - - @Test - public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException { - // given - final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.banner(null)); - final BidderCall httpCall = givenHttpCall(bidRequest, - mapper.writeValueAsString(null)); - - // when - final Result> result = target.makeBids(httpCall, bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).isEmpty(); - } - - @Test - public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull() throws JsonProcessingException { - // given - final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.banner(null)); - final BidderCall httpCall = givenHttpCall(bidRequest, - mapper.writeValueAsString(BidResponse.builder().build())); - - // when - final Result> result = target.makeBids(httpCall, bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).isEmpty(); - } - - @Test - public void makeBidsShouldReturnBannerBidIfBannerIsPresent() throws JsonProcessingException { - // given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(Imp.builder().id("123").build())) - .build(); - final BidderCall httpCall = givenHttpCall( - bidRequest, - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123")))); - - // when - final Result> result = target.makeBids(httpCall, bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .containsOnly(BidderBid.of(Bid.builder().impid("123").build(), banner, "USD")); - } - - @Test - public void makeBidsShouldReturnVideoBidIfVideoIsPresent() throws JsonProcessingException { - // given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("123") - .video(Video.builder().build()) - .build())) - .build(); - - final BidderCall httpCall = givenHttpCall( - bidRequest, - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123")))); - - // when - final Result> result = target.makeBids(httpCall, bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .containsExactly(BidderBid.of(Bid.builder().impid("123").build(), video, "USD")); - } - @Test public void makeHttpRequestsShouldConvertCurrencyIfRequestCurrencyDoesNotMatchBidderCurrency() { // given @@ -276,56 +188,6 @@ public void makeHttpRequestsShouldNotReturnErrorIfNativePresent() { assertThat(result.getValue()).hasSize(1); } - @Test - public void makeBidsShouldParseNativeAdmData() throws JsonProcessingException { - // given - final BidRequest bidRequest = givenBidRequest( - impBuilder -> impBuilder.id("123") - .banner(null) - .xNative(Native.builder().build())); - final ObjectNode admNode = mapper.createObjectNode(); - final ObjectNode nativeNode = mapper.createObjectNode(); - nativeNode.put("property1", "value1"); - admNode.set("native", nativeNode); - final BidderCall httpCall = givenHttpCall(bidRequest, - mapper.writeValueAsString( - givenBidResponse(bidBuilder -> bidBuilder.impid("123") - .adm(admNode.toString())))); - - // when - final Result> result = target.makeBids(httpCall, bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - - assertThat(result.getValue()) - .extracting(BidderBid::getBid) - .extracting(Bid::getAdm) - .containsExactly("{\"property1\":\"value1\"}"); - } - - @Test - public void makeBidsShouldReturnBidWithResolvedMacros() throws JsonProcessingException { - // given - final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.banner(null)); - final BidderCall httpCall = givenHttpCall(null, - mapper.writeValueAsString(givenBidResponse( - bidBuilder -> bidBuilder - .nurl("nurl:${AUCTION_PRICE}") - .adm("adm:${AUCTION_PRICE}") - .price(BigDecimal.valueOf(12.34))))); - - // when - final Result> result = target.makeBids(httpCall, bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .extracting(BidderBid::getBid) - .extracting(Bid::getNurl, Bid::getAdm) - .containsExactly(tuple("nurl:12.34", "adm:12.34")); - } - @Test public void makeHttpRequestsShouldCreateSiteAndPublisherWhenBidRequestHasNoSite() { // given @@ -518,6 +380,273 @@ public void makeHttpRequestsShouldAlwaysRemovePmpField() { .containsOnlyNulls(); } + @Test + public void makeHttpRequestsShouldSetTagidFromGpid() { + // given + final BidRequest bidRequest = givenBidRequest( + bidReq -> bidReq.id("request_id"), + imp -> imp.id("imp123").ext(givenRtbhouseExt(node -> node.put("gpid", "gpid_value"))), + identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getTagid) + .containsExactly("gpid_value"); + } + + @Test + public void makeHttpRequestsShouldSetTagidFromAdserverAdslot() { + // given + final BidRequest bidRequest = givenBidRequest( + bidReq -> bidReq.id("request_id"), + imp -> imp.id("imp123") + .ext(givenRtbhouseExt(node -> + node.set("data", mapper.valueToTree( + Map.of("adserver", Map.of("adslot", "adslot_value")))))), + identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getTagid) + .containsExactly("adslot_value"); + } + + @Test + public void makeHttpRequestsShouldSetTagidFromPbadslot() { + // given + final BidRequest bidRequest = givenBidRequest( + bidReq -> bidReq.id("request_id"), + imp -> imp.id("imp123") + .ext(givenRtbhouseExt(node -> + node.set("data", mapper.valueToTree(Map.of("pbadslot", "pbadslot_value"))))), + identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getTagid) + .containsExactly("pbadslot_value"); + } + + @Test + public void makeHttpRequestsShouldSetTagidFromImpIdWhenNoOtherFields() { + // given + final BidRequest bidRequest = givenBidRequest( + bidReq -> bidReq.id("request_id"), + imp -> imp.id("imp123"), + identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getTagid) + .containsExactly("imp123"); + } + + @Test + public void makeHttpRequestsShouldSetTagidToNullWhenNoFieldsAvailable() { + // given + final BidRequest bidRequest = givenBidRequest( + bidReq -> bidReq.id("request_id"), + imp -> imp.id(null), + identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getTagid) + .containsOnlyNulls(); + } + + @Test + public void makeHttpRequestsShouldPreserveExistingTagid() { + // given + final BidRequest bidRequest = givenBidRequest( + bidReq -> bidReq.id("request_id"), + imp -> imp.id("imp123") + .tagid("existing_tagid") + .ext(givenRtbhouseExt(node -> node.put("gpid", "gpid_value"))), + identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getTagid) + .containsExactly("existing_tagid"); + } + + @Test + public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { + // given + final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.banner(null)); + final BidderCall httpCall = givenHttpCall(bidRequest, "invalid"); + + // when + final Result> result = target.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).hasSize(1); + assertThat(result.getErrors().getFirst().getMessage()).startsWith("Failed to decode: Unrecognized token"); + assertThat(result.getErrors().getFirst().getType()).isEqualTo(BidderError.Type.bad_server_response); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException { + // given + final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.banner(null)); + final BidderCall httpCall = givenHttpCall(bidRequest, + mapper.writeValueAsString(null)); + + // when + final Result> result = target.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull() throws JsonProcessingException { + // given + final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.banner(null)); + final BidderCall httpCall = givenHttpCall(bidRequest, + mapper.writeValueAsString(BidResponse.builder().build())); + + // when + final Result> result = target.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnBannerBidIfBannerIsPresent() throws JsonProcessingException { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder().id("123").build())) + .build(); + final BidderCall httpCall = givenHttpCall( + bidRequest, + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder.impid("123")))); + + // when + final Result> result = target.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .containsOnly(BidderBid.of(Bid.builder().impid("123").build(), banner, "USD")); + } + + @Test + public void makeBidsShouldReturnVideoBidIfVideoIsPresent() throws JsonProcessingException { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder() + .id("123") + .video(Video.builder().build()) + .build())) + .build(); + + final BidderCall httpCall = givenHttpCall( + bidRequest, + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder.impid("123")))); + + // when + final Result> result = target.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .containsExactly(BidderBid.of(Bid.builder().impid("123").build(), video, "USD")); + } + + @Test + public void makeBidsShouldParseNativeAdmData() throws JsonProcessingException { + // given + final BidRequest bidRequest = givenBidRequest( + impBuilder -> impBuilder.id("123") + .banner(null) + .xNative(Native.builder().build())); + final ObjectNode admNode = mapper.createObjectNode(); + final ObjectNode nativeNode = mapper.createObjectNode(); + nativeNode.put("property1", "value1"); + admNode.set("native", nativeNode); + final BidderCall httpCall = givenHttpCall(bidRequest, + mapper.writeValueAsString( + givenBidResponse(bidBuilder -> bidBuilder.impid("123") + .adm(admNode.toString())))); + + // when + final Result> result = target.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + + assertThat(result.getValue()) + .extracting(BidderBid::getBid) + .extracting(Bid::getAdm) + .containsExactly("{\"property1\":\"value1\"}"); + } + + @Test + public void makeBidsShouldReturnBidWithResolvedMacros() throws JsonProcessingException { + // given + final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.banner(null)); + final BidderCall httpCall = givenHttpCall(null, + mapper.writeValueAsString(givenBidResponse( + bidBuilder -> bidBuilder + .nurl("nurl:${AUCTION_PRICE}") + .adm("adm:${AUCTION_PRICE}") + .price(BigDecimal.valueOf(12.34))))); + + // when + final Result> result = target.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(BidderBid::getBid) + .extracting(Bid::getNurl, Bid::getAdm) + .containsExactly(tuple("nurl:12.34", "adm:12.34")); + } + private static BidResponse givenBidResponse(Function bidCustomizer) { return BidResponse.builder() .cur("USD") @@ -562,4 +691,12 @@ private static Imp givenImp(Function impCustomiz .build())))) .build(); } + + private static ObjectNode givenRtbhouseExt(UnaryOperator extCustomizer) { + final ObjectNode extNode = mapper.valueToTree(ExtPrebid.of(null, ExtImpRtbhouse.builder() + .publisherId("publisherId") + .region("region") + .build())); + return extCustomizer.apply(extNode); + } } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/rtbhouse/test-rtbhouse-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/rtbhouse/test-rtbhouse-bid-request.json index e63afde3937..28d0438942f 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/rtbhouse/test-rtbhouse-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/rtbhouse/test-rtbhouse-bid-request.json @@ -13,7 +13,8 @@ "bidder": { "publisherId": "publisherId" } - } + }, + "tagid": "imp_id" } ], "source": { From 39b8abe907cb76e155d7094e17e53b39e24ccfb5 Mon Sep 17 00:00:00 2001 From: Dubyk Danylo <45672370+CTMBNara@users.noreply.github.com> Date: Wed, 14 Jan 2026 15:58:26 +0100 Subject: [PATCH 11/38] Refactoring: Add bidder request post-processing (#4322) --- .../server/auction/ExchangeService.java | 99 ++++------- .../BidderRequestCleaner.java | 37 ++++ .../BidderRequestCurrencyBlocker.java | 54 ++++++ .../BidderRequestMediaFilter.java | 139 +++++++++++++++ .../BidderRequestPostProcessingResult.java | 20 +++ .../BidderRequestPostProcessor.java | 13 ++ .../BidderRequestPreferredMediaProcessor.java | 155 ++++++++++++++++ .../BidderRequestRejectedException.java | 20 +++ .../CompositeBidderRequestPostProcessor.java | 46 +++++ .../BidderMediaTypeProcessor.java | 134 -------------- .../CompositeMediaTypeProcessor.java | 45 ----- .../MediaTypeProcessingResult.java | 25 --- .../MediaTypeProcessor.java | 10 -- .../MultiFormatMediaTypeProcessor.java | 143 --------------- .../org/prebid/server/bidder/BidderInfo.java | 6 +- .../spring/config/ServiceConfiguration.java | 47 +++-- .../server/auction/ExchangeServiceTest.java | 106 +++++------ .../BidderRequestCleanerTest.java | 69 ++++++++ .../BidderRequestCurrencyBlockerTest.java | 165 ++++++++++++++++++ .../BidderRequestMediaFilterTest.java} | 89 ++++++---- ...erRequestPreferredMediaProcessorTest.java} | 136 ++++++++------- ...mpositeBidderRequestPostProcessorTest.java | 88 ++++++++++ .../CompositeMediaTypeProcessorTest.java | 96 ---------- 23 files changed, 1052 insertions(+), 690 deletions(-) create mode 100644 src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleaner.java create mode 100644 src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCurrencyBlocker.java create mode 100644 src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestMediaFilter.java create mode 100644 src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPostProcessingResult.java create mode 100644 src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPostProcessor.java create mode 100644 src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPreferredMediaProcessor.java create mode 100644 src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestRejectedException.java create mode 100644 src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/CompositeBidderRequestPostProcessor.java delete mode 100644 src/main/java/org/prebid/server/auction/mediatypeprocessor/BidderMediaTypeProcessor.java delete mode 100644 src/main/java/org/prebid/server/auction/mediatypeprocessor/CompositeMediaTypeProcessor.java delete mode 100644 src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessingResult.java delete mode 100644 src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessor.java delete mode 100644 src/main/java/org/prebid/server/auction/mediatypeprocessor/MultiFormatMediaTypeProcessor.java create mode 100644 src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleanerTest.java create mode 100644 src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCurrencyBlockerTest.java rename src/test/java/org/prebid/server/auction/{mediatypeprocessor/BidderMediaTypeProcessorTest.java => bidderrequestpostprocessor/BidderRequestMediaFilterTest.java} (61%) rename src/test/java/org/prebid/server/auction/{mediatypeprocessor/MultiFormatMediaTypeProcessorTest.java => bidderrequestpostprocessor/BidderRequestPreferredMediaProcessorTest.java} (64%) create mode 100644 src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/CompositeBidderRequestPostProcessorTest.java delete mode 100644 src/test/java/org/prebid/server/auction/mediatypeprocessor/CompositeMediaTypeProcessorTest.java diff --git a/src/main/java/org/prebid/server/auction/ExchangeService.java b/src/main/java/org/prebid/server/auction/ExchangeService.java index 8bea0bc315e..c4ae087d205 100644 --- a/src/main/java/org/prebid/server/auction/ExchangeService.java +++ b/src/main/java/org/prebid/server/auction/ExchangeService.java @@ -19,7 +19,6 @@ import io.vertx.core.CompositeFuture; import io.vertx.core.Future; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.collections4.ListUtils; import org.apache.commons.collections4.map.CaseInsensitiveMap; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; @@ -32,9 +31,9 @@ import org.prebid.server.activity.infrastructure.payload.impl.BidRequestActivityInvocationPayload; import org.prebid.server.auction.aliases.AlternateBidderCodesConfig; import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestPostProcessor; +import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestRejectedException; import org.prebid.server.auction.externalortb.StoredResponseProcessor; -import org.prebid.server.auction.mediatypeprocessor.MediaTypeProcessingResult; -import org.prebid.server.auction.mediatypeprocessor.MediaTypeProcessor; import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.AuctionParticipation; import org.prebid.server.auction.model.BidRejectionReason; @@ -51,7 +50,6 @@ import org.prebid.server.auction.versionconverter.OrtbVersion; import org.prebid.server.bidder.Bidder; import org.prebid.server.bidder.BidderCatalog; -import org.prebid.server.bidder.BidderInfo; import org.prebid.server.bidder.HttpBidderRequester; import org.prebid.server.bidder.Usersyncer; import org.prebid.server.bidder.model.BidderBid; @@ -144,7 +142,7 @@ public class ExchangeService { private final ImpAdjuster impAdjuster; private final SupplyChainResolver supplyChainResolver; private final DebugResolver debugResolver; - private final MediaTypeProcessor mediaTypeProcessor; + private final BidderRequestPostProcessor bidderRequestPostProcessor; private final UidUpdater uidUpdater; private final TimeoutResolver timeoutResolver; private final TimeoutFactory timeoutFactory; @@ -171,7 +169,7 @@ public ExchangeService(double logSamplingRate, ImpAdjuster impAdjuster, SupplyChainResolver supplyChainResolver, DebugResolver debugResolver, - MediaTypeProcessor mediaTypeProcessor, + BidderRequestPostProcessor bidderRequestPostProcessor, UidUpdater uidUpdater, TimeoutResolver timeoutResolver, TimeoutFactory timeoutFactory, @@ -198,7 +196,7 @@ public ExchangeService(double logSamplingRate, this.impAdjuster = Objects.requireNonNull(impAdjuster); this.supplyChainResolver = Objects.requireNonNull(supplyChainResolver); this.debugResolver = Objects.requireNonNull(debugResolver); - this.mediaTypeProcessor = Objects.requireNonNull(mediaTypeProcessor); + this.bidderRequestPostProcessor = Objects.requireNonNull(bidderRequestPostProcessor); this.uidUpdater = Objects.requireNonNull(uidUpdater); this.timeoutResolver = Objects.requireNonNull(timeoutResolver); this.timeoutFactory = Objects.requireNonNull(timeoutFactory); @@ -435,7 +433,7 @@ private void removeInvalidBidRejectionTrackers(Map BidderAliases aliases) { final Set bidderNames = new HashSet<>(bidRejectionTrackers.keySet()); - for (String bidder: bidderNames) { + for (String bidder : bidderNames) { if (!isValidBidder(bidder, aliases)) { bidRejectionTrackers.remove(bidder); logger.warn("Pre-rejected impressions of the bidder {} have been removed. " @@ -492,13 +490,12 @@ private Future> extractAuctionParticipations( .filter(bidder -> isBidderCallActivityAllowed(bidder, context)) .distinct() .toList(); - final Map> impBidderToStoredBidResponse = - storedResponseResult.getImpBidderToStoredBidResponse(); + return makeAuctionParticipation( bidders, context, aliases, - impBidderToStoredBidResponse, + storedResponseResult.getImpBidderToStoredBidResponse(), imps, bidderToMultiBid); } @@ -539,7 +536,7 @@ private Future> makeAuctionParticipation( final BidRequest bidRequest = context.getBidRequest(); final ExtRequest requestExt = bidRequest.getExt(); - final ExtRequestPrebid prebid = requestExt == null ? null : requestExt.getPrebid(); + final ExtRequestPrebid prebid = requestExt != null ? requestExt.getPrebid() : null; final Map biddersToConfigs = getBiddersToConfigs(prebid); final Map> eidPermissions = getEidPermissions(prebid); final Map> bidderToUserAndDevice = @@ -1157,69 +1154,39 @@ private Future processAndRequestBids(AuctionContext auctionConte Timeout timeout, BidderAliases aliases) { - final String bidderName = bidderRequest.getBidder(); - final MediaTypeProcessingResult mediaTypeProcessingResult = mediaTypeProcessor.process( - bidderRequest.getBidRequest(), bidderName, aliases, auctionContext.getAccount()); - final List mediaTypeProcessingErrors = mediaTypeProcessingResult.getErrors(); - if (mediaTypeProcessingResult.isRejected()) { - return processReject( - auctionContext, - BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE, - mediaTypeProcessingErrors, - bidderName); - } - if (isUnacceptableCurrency(auctionContext, aliases.resolveBidder(bidderName))) { - return processReject( - auctionContext, - BidRejectionReason.REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY, - List.of(BidderError.generic("No match between the configured currencies and bidRequest.cur")), - bidderName); - } - - return Future.succeededFuture(mediaTypeProcessingResult.getBidRequest()) - .map(bidderRequest::with) - .compose(modifiedBidderRequest -> invokeHooksAndRequestBids( - auctionContext, modifiedBidderRequest, timeout, aliases)) - .map(bidderResponse -> bidderResponse.with( - addWarnings(bidderResponse.getSeatBid(), mediaTypeProcessingErrors))); - } - - private boolean isUnacceptableCurrency(AuctionContext auctionContext, String originalBidderName) { - final List requestCurrencies = auctionContext.getBidRequest().getCur(); - final List bidAcceptableCurrencies = - Optional.ofNullable(bidderCatalog.bidderInfoByName(originalBidderName)) - .map(BidderInfo::getCurrencyAccepted) - .orElse(null); - - if (CollectionUtils.isEmpty(requestCurrencies) || CollectionUtils.isEmpty(bidAcceptableCurrencies)) { - return false; - } - - return !CollectionUtils.containsAny(requestCurrencies, bidAcceptableCurrencies); - } - - private static Future processReject(AuctionContext auctionContext, - BidRejectionReason bidRejectionReason, - List warnings, - String bidderName) { - - auctionContext.getBidRejectionTrackers() - .get(bidderName) - .rejectAll(bidRejectionReason); - final BidderSeatBid bidderSeatBid = BidderSeatBid.builder() - .warnings(warnings) - .build(); - return Future.succeededFuture(BidderResponse.of(bidderName, bidderSeatBid, 0)); + return bidderRequestPostProcessor.process(bidderRequest, aliases, auctionContext) + .compose(result -> invokeHooksAndRequestBids(auctionContext, result.getValue(), timeout, aliases) + .map(response -> response.with(addWarnings(response.getSeatBid(), result.getErrors())))) + .recover(throwable -> recoverBidderRequestRejection( + auctionContext, bidderRequest.getBidder(), throwable)); } private static BidderSeatBid addWarnings(BidderSeatBid seatBid, List warnings) { return CollectionUtils.isNotEmpty(warnings) ? seatBid.toBuilder() - .warnings(ListUtils.union(warnings, seatBid.getWarnings())) + .warnings(ListUtil.union(warnings, seatBid.getWarnings())) .build() : seatBid; } + private static Future recoverBidderRequestRejection(AuctionContext auctionContext, + String bidderName, + Throwable throwable) { + + if (throwable instanceof BidderRequestRejectedException rejection) { + auctionContext.getBidRejectionTrackers() + .get(bidderName) + .rejectAll(rejection.getRejectionReason()); + final BidderSeatBid bidderSeatBid = BidderSeatBid.builder() + .warnings(rejection.getErrors()) + .build(); + + return Future.succeededFuture(BidderResponse.of(bidderName, bidderSeatBid, 0)); + } + + return Future.failedFuture(throwable); + } + private Future invokeHooksAndRequestBids(AuctionContext auctionContext, BidderRequest bidderRequest, Timeout timeout, diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleaner.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleaner.java new file mode 100644 index 00000000000..e470fcbbdd0 --- /dev/null +++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleaner.java @@ -0,0 +1,37 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.BidRequest; +import io.vertx.core.Future; +import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.BidderRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; + +public class BidderRequestCleaner implements BidderRequestPostProcessor { + + @Override + public Future process(BidderRequest bidderRequest, + BidderAliases aliases, + AuctionContext auctionContext) { + + final BidRequest bidRequest = bidderRequest.getBidRequest(); + final ExtRequest ext = bidRequest.getExt(); + final ExtRequestPrebid extPrebid = ext != null ? ext.getPrebid() : null; + final ObjectNode bidderControls = extPrebid != null ? extPrebid.getBiddercontrols() : null; + + if (bidderControls == null) { + return resultOf(bidderRequest); + } + + final ExtRequest cleanedExt = ExtRequest.of(extPrebid.toBuilder().biddercontrols(null).build()); + cleanedExt.addProperties(ext.getProperties()); + + return resultOf(bidderRequest.with(bidRequest.toBuilder().ext(cleanedExt).build())); + } + + private static Future resultOf(BidderRequest bidderRequest) { + return Future.succeededFuture(BidderRequestPostProcessingResult.withValue(bidderRequest)); + } +} diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCurrencyBlocker.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCurrencyBlocker.java new file mode 100644 index 00000000000..a131d8a6a50 --- /dev/null +++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCurrencyBlocker.java @@ -0,0 +1,54 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import com.iab.openrtb.request.BidRequest; +import io.vertx.core.Future; +import org.apache.commons.collections4.CollectionUtils; +import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.BidRejectionReason; +import org.prebid.server.auction.model.BidderRequest; +import org.prebid.server.bidder.BidderCatalog; +import org.prebid.server.bidder.BidderInfo; +import org.prebid.server.bidder.model.BidderError; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +public class BidderRequestCurrencyBlocker implements BidderRequestPostProcessor { + + private final BidderCatalog bidderCatalog; + + public BidderRequestCurrencyBlocker(BidderCatalog bidderCatalog) { + this.bidderCatalog = Objects.requireNonNull(bidderCatalog); + } + + @Override + public Future process(BidderRequest bidderRequest, + BidderAliases aliases, + AuctionContext auctionContext) { + + if (isAcceptableCurrency(bidderRequest.getBidRequest(), aliases.resolveBidder(bidderRequest.getBidder()))) { + return Future.succeededFuture(BidderRequestPostProcessingResult.withValue(bidderRequest)); + } + + return Future.failedFuture(new BidderRequestRejectedException( + BidRejectionReason.REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY, + Collections.singletonList( + BidderError.generic("No match between the configured currencies and bidRequest.cur")))); + } + + private boolean isAcceptableCurrency(BidRequest bidRequest, String originalBidderName) { + final List requestCurrencies = bidRequest.getCur(); + final Set bidAcceptableCurrencies = + Optional.ofNullable(bidderCatalog.bidderInfoByName(originalBidderName)) + .map(BidderInfo::getCurrencyAccepted) + .orElse(null); + + return CollectionUtils.isEmpty(requestCurrencies) + || CollectionUtils.isEmpty(bidAcceptableCurrencies) + || requestCurrencies.stream().anyMatch(bidAcceptableCurrencies::contains); + } +} diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestMediaFilter.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestMediaFilter.java new file mode 100644 index 00000000000..b3946a11081 --- /dev/null +++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestMediaFilter.java @@ -0,0 +1,139 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import com.iab.openrtb.request.Audio; +import com.iab.openrtb.request.Banner; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.Native; +import com.iab.openrtb.request.Video; +import io.vertx.core.Future; +import org.apache.commons.lang3.ObjectUtils; +import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.BidRejectionReason; +import org.prebid.server.auction.model.BidderRequest; +import org.prebid.server.bidder.BidderCatalog; +import org.prebid.server.bidder.BidderInfo; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.spring.config.bidder.model.MediaType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class BidderRequestMediaFilter implements BidderRequestPostProcessor { + + private static final EnumSet NONE_OF_MEDIA_TYPES = EnumSet.noneOf(MediaType.class); + + private final BidderCatalog bidderCatalog; + + public BidderRequestMediaFilter(BidderCatalog bidderCatalog) { + this.bidderCatalog = Objects.requireNonNull(bidderCatalog); + } + + @Override + public Future process(BidderRequest bidderRequest, + BidderAliases aliases, + AuctionContext auctionContext) { + + final String resolvedBidderName = aliases.resolveBidder(bidderRequest.getBidder()); + final BidRequest bidRequest = bidderRequest.getBidRequest(); + final Set supportedMediaTypes = extractSupportedMediaTypes(bidRequest, resolvedBidderName); + if (supportedMediaTypes.isEmpty()) { + return rejected(Collections.singletonList( + BidderError.badInput("Bidder does not support any media types."))); + } + + final List errors = new ArrayList<>(); + final BidRequest modifiedBidRequest = processBidRequest(bidRequest, supportedMediaTypes, errors); + final BidderRequest modifiedBidderRequest = modifiedBidRequest != null + ? bidderRequest.with(modifiedBidRequest) + : null; + + return modifiedBidderRequest != null + ? Future.succeededFuture(BidderRequestPostProcessingResult.of(modifiedBidderRequest, errors)) + : rejected(errors); + } + + private static Future rejected(List errors) { + return Future.failedFuture( + new BidderRequestRejectedException(BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE, errors)); + } + + private Set extractSupportedMediaTypes(BidRequest bidRequest, String bidderName) { + final BidderInfo.CapabilitiesInfo capabilitiesInfo = bidderCatalog + .bidderInfoByName(bidderName) + .getCapabilities(); + + final BidderInfo.PlatformInfo site = bidRequest.getSite() != null ? capabilitiesInfo.getSite() : null; + final BidderInfo.PlatformInfo app = bidRequest.getApp() != null ? capabilitiesInfo.getApp() : null; + final BidderInfo.PlatformInfo dooh = bidRequest.getDooh() != null ? capabilitiesInfo.getDooh() : null; + + return Stream.of(site, app, dooh) + .filter(Objects::nonNull) + .findFirst() + .map(BidderInfo.PlatformInfo::getMediaTypes) + .filter(mediaTypes -> !mediaTypes.isEmpty()) + .map(EnumSet::copyOf) + .orElse(NONE_OF_MEDIA_TYPES); + } + + private static BidRequest processBidRequest(BidRequest bidRequest, + Set supportedMediaTypes, + List errors) { + + final List modifiedImps = bidRequest.getImp().stream() + .map(imp -> processImp(imp, supportedMediaTypes, errors)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + if (modifiedImps.isEmpty()) { + errors.add(BidderError.badInput("Bid request contains 0 impressions after filtering.")); + return null; + } + + return bidRequest.toBuilder().imp(modifiedImps).build(); + } + + private static Imp processImp(Imp imp, Set supportedMediaTypes, List errors) { + final Set impMediaTypes = getMediaTypes(imp); + if (supportedMediaTypes.containsAll(impMediaTypes)) { + return imp; + } + + final Banner banner = supportedMediaTypes.contains(MediaType.BANNER) ? imp.getBanner() : null; + final Video video = supportedMediaTypes.contains(MediaType.VIDEO) ? imp.getVideo() : null; + final Audio audio = supportedMediaTypes.contains(MediaType.AUDIO) ? imp.getAudio() : null; + final Native xNative = supportedMediaTypes.contains(MediaType.NATIVE) ? imp.getXNative() : null; + + if (ObjectUtils.allNull(banner, video, audio, xNative)) { + errors.add(BidderError.badInput(""" + Imp %s does not have a supported media type \ + and has been removed from the request for this bidder.""".formatted(imp.getId()))); + + return null; + } + + return imp.toBuilder() + .banner(banner) + .video(video) + .audio(audio) + .xNative(xNative) + .build(); + } + + private static Set getMediaTypes(Imp imp) { + return Stream.of( + imp.getBanner() != null ? MediaType.BANNER : null, + imp.getVideo() != null ? MediaType.VIDEO : null, + imp.getAudio() != null ? MediaType.AUDIO : null, + imp.getXNative() != null ? MediaType.NATIVE : null) + .filter(Objects::nonNull) + .collect(Collectors.toCollection(() -> EnumSet.noneOf(MediaType.class))); + } +} diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPostProcessingResult.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPostProcessingResult.java new file mode 100644 index 00000000000..f38d9a1a423 --- /dev/null +++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPostProcessingResult.java @@ -0,0 +1,20 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import lombok.Value; +import org.prebid.server.auction.model.BidderRequest; +import org.prebid.server.bidder.model.BidderError; + +import java.util.Collections; +import java.util.List; + +@Value(staticConstructor = "of") +public class BidderRequestPostProcessingResult { + + BidderRequest value; + + List errors; + + public static BidderRequestPostProcessingResult withValue(BidderRequest bidderRequest) { + return BidderRequestPostProcessingResult.of(bidderRequest, Collections.emptyList()); + } +} diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPostProcessor.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPostProcessor.java new file mode 100644 index 00000000000..e2fc97f225f --- /dev/null +++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPostProcessor.java @@ -0,0 +1,13 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import io.vertx.core.Future; +import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.BidderRequest; + +public interface BidderRequestPostProcessor { + + Future process(BidderRequest bidderRequest, + BidderAliases aliases, + AuctionContext auctionContext); +} diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPreferredMediaProcessor.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPreferredMediaProcessor.java new file mode 100644 index 00000000000..fb685298a4a --- /dev/null +++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPreferredMediaProcessor.java @@ -0,0 +1,155 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import com.fasterxml.jackson.databind.JsonNode; +import com.iab.openrtb.request.Audio; +import com.iab.openrtb.request.Banner; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.Native; +import com.iab.openrtb.request.Video; +import io.vertx.core.Future; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.BidRejectionReason; +import org.prebid.server.auction.model.BidderRequest; +import org.prebid.server.bidder.BidderCatalog; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.proto.openrtb.ext.request.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; +import org.prebid.server.settings.model.Account; +import org.prebid.server.settings.model.AccountAuctionConfig; +import org.prebid.server.spring.config.bidder.model.MediaType; +import org.prebid.server.util.StreamUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +public class BidderRequestPreferredMediaProcessor implements BidderRequestPostProcessor { + + private static final String PREF_MTYPE_FIELD = "prefmtype"; + + private final BidderCatalog bidderCatalog; + + public BidderRequestPreferredMediaProcessor(BidderCatalog bidderCatalog) { + this.bidderCatalog = Objects.requireNonNull(bidderCatalog); + } + + @Override + public Future process(BidderRequest bidderRequest, + BidderAliases aliases, + AuctionContext auctionContext) { + + final String bidderName = bidderRequest.getBidder(); + final BidRequest bidRequest = bidderRequest.getBidRequest(); + + final String resolvedBidderName = aliases.resolveBidder(bidderName); + if (isMultiFormatSupported(resolvedBidderName)) { + return noAction(bidderRequest); + } + + final Optional preferredMediaType = preferredMediaType(bidRequest, bidderName) + .or(() -> preferredMediaType(auctionContext.getAccount(), resolvedBidderName)); + if (preferredMediaType.isEmpty()) { + return noAction(bidderRequest); + } + + final List errors = new ArrayList<>(); + final BidRequest modifiedBidRequest = processBidRequest(bidRequest, preferredMediaType.get(), errors); + final BidderRequest modifiedBidderRequest = modifiedBidRequest != null + ? bidderRequest.with(modifiedBidRequest) + : null; + + return modifiedBidderRequest != null + ? Future.succeededFuture(BidderRequestPostProcessingResult.of(modifiedBidderRequest, errors)) + : Future.failedFuture(new BidderRequestRejectedException( + BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE, errors)); + } + + private static Future noAction(BidderRequest bidderRequest) { + return Future.succeededFuture(BidderRequestPostProcessingResult.withValue(bidderRequest)); + } + + private boolean isMultiFormatSupported(String bidder) { + return bidderCatalog.bidderInfoByName(bidder).getOrtb().isMultiFormatSupported(); + } + + private static Optional preferredMediaType(BidRequest bidRequest, String bidderName) { + return Optional.ofNullable(bidRequest.getExt()) + .map(ExtRequest::getPrebid) + .map(ExtRequestPrebid::getBiddercontrols) + .flatMap(bidders -> getBidder(bidderName, bidders)) + .map(bidder -> bidder.get(PREF_MTYPE_FIELD)) + .filter(JsonNode::isTextual) + .map(JsonNode::textValue) + .map(MediaType::of); + } + + private static Optional preferredMediaType(Account account, String bidderName) { + return Optional.ofNullable(account.getAuction()) + .map(AccountAuctionConfig::getPreferredMediaTypes) + .map(preferredMediaTypes -> preferredMediaTypes.get(bidderName)); + } + + private static Optional getBidder(String bidderName, JsonNode biddersNode) { + return StreamUtil.asStream(biddersNode.fieldNames()) + .filter(fieldName -> StringUtils.equalsIgnoreCase(bidderName, fieldName)) + .map(biddersNode::get) + .findAny(); + } + + private static BidRequest processBidRequest(BidRequest bidRequest, + MediaType preferredMediaType, + List errors) { + + final List modifiedImps = bidRequest.getImp().stream() + .map(imp -> processImp(imp, preferredMediaType, errors)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + if (modifiedImps.isEmpty()) { + errors.add(BidderError.badInput("Bid request contains 0 impressions after filtering.")); + return null; + } + + return bidRequest.toBuilder().imp(modifiedImps).build(); + } + + private static Imp processImp(Imp imp, MediaType preferredMediaType, List errors) { + if (!isMultiFormat(imp)) { + return imp; + } + + final Banner banner = preferredMediaType == MediaType.BANNER ? imp.getBanner() : null; + final Video video = preferredMediaType == MediaType.VIDEO ? imp.getVideo() : null; + final Audio audio = preferredMediaType == MediaType.AUDIO ? imp.getAudio() : null; + final Native xNative = preferredMediaType == MediaType.NATIVE ? imp.getXNative() : null; + + if (ObjectUtils.allNull(banner, video, audio, xNative)) { + errors.add(BidderError.badInput(""" + Imp %s does not have a media type after filtering \ + and has been removed from the request for this bidder.""".formatted(imp.getId()))); + + return null; + } + + return imp.toBuilder() + .banner(banner) + .video(video) + .audio(audio) + .xNative(xNative) + .build(); + } + + private static boolean isMultiFormat(Imp imp) { + int count = 0; + return (imp.getBanner() != null && ++count > 1) + || (imp.getVideo() != null && ++count > 1) + || (imp.getAudio() != null && ++count > 1) + || (imp.getXNative() != null && ++count > 1); + } +} diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestRejectedException.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestRejectedException.java new file mode 100644 index 00000000000..6beeaa5069f --- /dev/null +++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestRejectedException.java @@ -0,0 +1,20 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import lombok.Getter; +import org.prebid.server.auction.model.BidRejectionReason; +import org.prebid.server.bidder.model.BidderError; + +import java.util.List; +import java.util.Objects; + +@Getter +public class BidderRequestRejectedException extends RuntimeException { + + private final BidRejectionReason rejectionReason; + private final List errors; + + public BidderRequestRejectedException(BidRejectionReason bidRejectionReason, List errors) { + this.rejectionReason = Objects.requireNonNull(bidRejectionReason); + this.errors = Objects.requireNonNull(errors); + } +} diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/CompositeBidderRequestPostProcessor.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/CompositeBidderRequestPostProcessor.java new file mode 100644 index 00000000000..b483c2ad63e --- /dev/null +++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/CompositeBidderRequestPostProcessor.java @@ -0,0 +1,46 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import io.vertx.core.Future; +import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.BidderRequest; +import org.prebid.server.util.ListUtil; + +import java.util.List; +import java.util.Objects; + +public class CompositeBidderRequestPostProcessor implements BidderRequestPostProcessor { + + private final List bidderRequestPostProcessors; + + public CompositeBidderRequestPostProcessor(List bidderRequestPostProcessors) { + this.bidderRequestPostProcessors = Objects.requireNonNull(bidderRequestPostProcessors); + } + + @Override + public Future process(BidderRequest bidderRequest, + BidderAliases aliases, + AuctionContext auctionContext) { + + Future result = initialResult(bidderRequest); + for (BidderRequestPostProcessor bidderRequestPostProcessor : bidderRequestPostProcessors) { + result = result.compose(previous -> + bidderRequestPostProcessor.process(previous.getValue(), aliases, auctionContext) + .map(current -> mergeErrors(previous, current))); + } + + return result; + } + + private static Future initialResult(BidderRequest bidderRequest) { + return Future.succeededFuture(BidderRequestPostProcessingResult.withValue(bidderRequest)); + } + + private static BidderRequestPostProcessingResult mergeErrors(BidderRequestPostProcessingResult previous, + BidderRequestPostProcessingResult current) { + + return BidderRequestPostProcessingResult.of( + current.getValue(), + ListUtil.union(previous.getErrors(), current.getErrors())); + } +} diff --git a/src/main/java/org/prebid/server/auction/mediatypeprocessor/BidderMediaTypeProcessor.java b/src/main/java/org/prebid/server/auction/mediatypeprocessor/BidderMediaTypeProcessor.java deleted file mode 100644 index 17828059175..00000000000 --- a/src/main/java/org/prebid/server/auction/mediatypeprocessor/BidderMediaTypeProcessor.java +++ /dev/null @@ -1,134 +0,0 @@ -package org.prebid.server.auction.mediatypeprocessor; - -import com.iab.openrtb.request.BidRequest; -import com.iab.openrtb.request.Imp; -import org.apache.commons.collections4.SetUtils; -import org.prebid.server.auction.aliases.BidderAliases; -import org.prebid.server.bidder.BidderCatalog; -import org.prebid.server.bidder.BidderInfo; -import org.prebid.server.bidder.model.BidderError; -import org.prebid.server.settings.model.Account; -import org.prebid.server.spring.config.bidder.model.MediaType; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * {@link BidderMediaTypeProcessor} is an implementation of {@link MediaTypeProcessor} that - * can be used to remove media types from {@link Imp} unsupported by specific bidder. - */ -public class BidderMediaTypeProcessor implements MediaTypeProcessor { - - private static final EnumSet NONE_OF_MEDIA_TYPES = EnumSet.noneOf(MediaType.class); - - private final BidderCatalog bidderCatalog; - - public BidderMediaTypeProcessor(BidderCatalog bidderCatalog) { - this.bidderCatalog = Objects.requireNonNull(bidderCatalog); - } - - @Override - public MediaTypeProcessingResult process(BidRequest bidRequest, - String bidderName, - BidderAliases aliases, - Account account) { - final String resolvedBidderName = aliases.resolveBidder(bidderName); - final Set supportedMediaTypes = extractSupportedMediaTypes(bidRequest, resolvedBidderName); - if (supportedMediaTypes.isEmpty()) { - return MediaTypeProcessingResult.rejected(Collections.singletonList( - BidderError.badInput("Bidder does not support any media types."))); - } - - final List errors = new ArrayList<>(); - final BidRequest modifiedBidRequest = processBidRequest(bidRequest, supportedMediaTypes, errors); - - return modifiedBidRequest != null - ? MediaTypeProcessingResult.succeeded(modifiedBidRequest, errors) - : MediaTypeProcessingResult.rejected(errors); - } - - private Set extractSupportedMediaTypes(BidRequest bidRequest, String bidderName) { - final BidderInfo.CapabilitiesInfo capabilitiesInfo = bidderCatalog.bidderInfoByName(bidderName) - .getCapabilities(); - - final Supplier fetchSupportedMediaTypes; - if (bidRequest.getSite() != null) { - fetchSupportedMediaTypes = capabilitiesInfo::getSite; - } else if (bidRequest.getApp() != null) { - fetchSupportedMediaTypes = capabilitiesInfo::getApp; - } else { - fetchSupportedMediaTypes = capabilitiesInfo::getDooh; - } - - return Optional.ofNullable(fetchSupportedMediaTypes.get()) - .map(BidderInfo.PlatformInfo::getMediaTypes) - .filter(mediaTypes -> !mediaTypes.isEmpty()) - .map(EnumSet::copyOf) - .orElse(NONE_OF_MEDIA_TYPES); - } - - private BidRequest processBidRequest(BidRequest bidRequest, - Set supportedMediaTypes, - List errors) { - - final List modifiedImps = bidRequest.getImp().stream() - .map(imp -> processImp(imp, supportedMediaTypes, errors)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - - if (modifiedImps.isEmpty()) { - errors.add(BidderError.badInput("Bid request contains 0 impressions after filtering.")); - return null; - } - - return bidRequest.toBuilder().imp(modifiedImps).build(); - } - - private static Imp processImp(Imp imp, Set supportedMediaTypes, List errors) { - final Set impMediaTypes = getMediaTypes(imp); - final Set unsupportedMediaTypes = SetUtils.difference(impMediaTypes, supportedMediaTypes); - - if (unsupportedMediaTypes.isEmpty()) { - return imp; - } - - if (impMediaTypes.equals(unsupportedMediaTypes)) { - errors.add(BidderError.badInput("Imp " + imp.getId() + " does not have a supported media type " - + "and has been removed from the request for this bidder.")); - - return null; - } - - final Imp.ImpBuilder impBuilder = imp.toBuilder(); - unsupportedMediaTypes.forEach(unsupportedMediaType -> removeMediaType(impBuilder, unsupportedMediaType)); - - return impBuilder.build(); - } - - private static Set getMediaTypes(Imp imp) { - return Stream.of( - imp.getBanner() != null ? MediaType.BANNER : null, - imp.getVideo() != null ? MediaType.VIDEO : null, - imp.getAudio() != null ? MediaType.AUDIO : null, - imp.getXNative() != null ? MediaType.NATIVE : null) - .filter(Objects::nonNull) - .collect(Collectors.toCollection(() -> EnumSet.noneOf(MediaType.class))); - } - - private static void removeMediaType(Imp.ImpBuilder impBuilder, MediaType mediaType) { - switch (mediaType) { - case BANNER -> impBuilder.banner(null); - case VIDEO -> impBuilder.video(null); - case AUDIO -> impBuilder.audio(null); - case NATIVE -> impBuilder.xNative(null); - } - } -} diff --git a/src/main/java/org/prebid/server/auction/mediatypeprocessor/CompositeMediaTypeProcessor.java b/src/main/java/org/prebid/server/auction/mediatypeprocessor/CompositeMediaTypeProcessor.java deleted file mode 100644 index 5dbd2bb3ae6..00000000000 --- a/src/main/java/org/prebid/server/auction/mediatypeprocessor/CompositeMediaTypeProcessor.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.prebid.server.auction.mediatypeprocessor; - -import com.iab.openrtb.request.BidRequest; -import org.prebid.server.auction.aliases.BidderAliases; -import org.prebid.server.bidder.model.BidderError; -import org.prebid.server.settings.model.Account; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -public class CompositeMediaTypeProcessor implements MediaTypeProcessor { - - private final List mediaTypeProcessors; - - public CompositeMediaTypeProcessor(List mediaTypeProcessors) { - this.mediaTypeProcessors = Objects.requireNonNull(mediaTypeProcessors); - } - - @Override - public MediaTypeProcessingResult process(BidRequest originalBidRequest, - String bidderName, - BidderAliases aliases, - Account account) { - BidRequest bidRequest = originalBidRequest; - final List errors = new ArrayList<>(); - - for (MediaTypeProcessor mediaTypeProcessor : mediaTypeProcessors) { - final MediaTypeProcessingResult result = mediaTypeProcessor.process( - bidRequest, - bidderName, - aliases, - account); - - bidRequest = result.getBidRequest(); - errors.addAll(result.getErrors()); - - if (result.isRejected()) { - return MediaTypeProcessingResult.rejected(errors); - } - } - - return MediaTypeProcessingResult.succeeded(bidRequest, errors); - } -} diff --git a/src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessingResult.java b/src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessingResult.java deleted file mode 100644 index b9a6780d2e8..00000000000 --- a/src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessingResult.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.prebid.server.auction.mediatypeprocessor; - -import com.iab.openrtb.request.BidRequest; -import lombok.Value; -import org.prebid.server.bidder.model.BidderError; - -import java.util.List; - -@Value(staticConstructor = "of") -public class MediaTypeProcessingResult { - - BidRequest bidRequest; - - List errors; - - boolean rejected; - - public static MediaTypeProcessingResult succeeded(BidRequest bidRequest, List errors) { - return MediaTypeProcessingResult.of(bidRequest, errors, false); - } - - public static MediaTypeProcessingResult rejected(List errors) { - return MediaTypeProcessingResult.of(null, errors, true); - } -} diff --git a/src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessor.java b/src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessor.java deleted file mode 100644 index 0cf5f19bd6a..00000000000 --- a/src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessor.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.prebid.server.auction.mediatypeprocessor; - -import com.iab.openrtb.request.BidRequest; -import org.prebid.server.auction.aliases.BidderAliases; -import org.prebid.server.settings.model.Account; - -public interface MediaTypeProcessor { - - MediaTypeProcessingResult process(BidRequest bidRequest, String bidderName, BidderAliases aliases, Account account); -} diff --git a/src/main/java/org/prebid/server/auction/mediatypeprocessor/MultiFormatMediaTypeProcessor.java b/src/main/java/org/prebid/server/auction/mediatypeprocessor/MultiFormatMediaTypeProcessor.java deleted file mode 100644 index 0fda09f919e..00000000000 --- a/src/main/java/org/prebid/server/auction/mediatypeprocessor/MultiFormatMediaTypeProcessor.java +++ /dev/null @@ -1,143 +0,0 @@ -package org.prebid.server.auction.mediatypeprocessor; - -import com.fasterxml.jackson.databind.JsonNode; -import com.iab.openrtb.request.BidRequest; -import com.iab.openrtb.request.Imp; -import org.apache.commons.lang3.StringUtils; -import org.prebid.server.auction.aliases.BidderAliases; -import org.prebid.server.bidder.BidderCatalog; -import org.prebid.server.bidder.model.BidderError; -import org.prebid.server.proto.openrtb.ext.request.ExtRequest; -import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; -import org.prebid.server.settings.model.Account; -import org.prebid.server.settings.model.AccountAuctionConfig; -import org.prebid.server.spring.config.bidder.model.MediaType; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -public class MultiFormatMediaTypeProcessor implements MediaTypeProcessor { - - private static final String PREF_MTYPE_FIELD = "prefmtype"; - - private final BidderCatalog bidderCatalog; - - public MultiFormatMediaTypeProcessor(BidderCatalog bidderCatalog) { - this.bidderCatalog = Objects.requireNonNull(bidderCatalog); - } - - @Override - public MediaTypeProcessingResult process(BidRequest bidRequest, - String bidderName, - BidderAliases aliases, - Account account) { - final String resolvedBidderName = aliases.resolveBidder(bidderName); - //todo: ext.prebid.biddercontrols clean-up should NOT be here - // Suggestion: keep biddercontrols in the Auction Context - // and clean it up on the extraction auction participants step - final BidRequest.BidRequestBuilder bidRequestBuilder = Optional.ofNullable(bidRequest.getExt()) - .map(ExtRequest::getPrebid) - .map(prebid -> prebid.toBuilder().biddercontrols(null).build()) - .map(prebid -> { - final ExtRequest extRequest = ExtRequest.of(prebid); - extRequest.addProperties(bidRequest.getExt().getProperties()); - return extRequest; - }) - .map(extRequest -> bidRequest.toBuilder().ext(extRequest)) - .orElse(bidRequest.toBuilder()); - if (isMultiFormatSupported(resolvedBidderName)) { - return MediaTypeProcessingResult.succeeded(bidRequestBuilder.build(), Collections.emptyList()); - } - - final MediaType preferredMediaType = preferredMediaType(bidRequest, account, bidderName, resolvedBidderName); - if (preferredMediaType == null) { - return MediaTypeProcessingResult.succeeded(bidRequestBuilder.build(), Collections.emptyList()); - } - - final List errors = new ArrayList<>(); - final List updatedImps = bidRequest.getImp().stream() - .map(imp -> processImp(imp, preferredMediaType, errors)) - .filter(Objects::nonNull) - .toList(); - - if (updatedImps.isEmpty()) { - errors.add(BidderError.badInput("Bid request contains 0 impressions after filtering.")); - return MediaTypeProcessingResult.rejected(errors); - } - - return MediaTypeProcessingResult.succeeded(bidRequestBuilder.imp(updatedImps).build(), errors); - } - - private boolean isMultiFormatSupported(String bidder) { - return bidderCatalog.bidderInfoByName(bidder).getOrtb().isMultiFormatSupported(); - } - - private MediaType preferredMediaType(BidRequest bidRequest, - Account account, - String originalBidderName, - String resolvedBidderName) { - - return Optional.ofNullable(bidRequest.getExt()) - .map(ExtRequest::getPrebid) - .map(ExtRequestPrebid::getBiddercontrols) - .map(bidders -> getBidder(originalBidderName, bidders)) - .map(bidder -> bidder.get(PREF_MTYPE_FIELD)) - .filter(JsonNode::isTextual) - .map(JsonNode::textValue) - .map(MediaType::of) - .or(() -> Optional.ofNullable(account.getAuction()) - .map(AccountAuctionConfig::getPreferredMediaTypes) - .map(preferredMediaTypes -> preferredMediaTypes.get(resolvedBidderName))) - .orElse(null); - } - - private static JsonNode getBidder(String bidderName, JsonNode biddersNode) { - final Iterator fieldNames = biddersNode.fieldNames(); - while (fieldNames.hasNext()) { - final String fieldName = fieldNames.next(); - if (StringUtils.equalsIgnoreCase(bidderName, fieldName)) { - return biddersNode.get(fieldName); - } - } - return null; - } - - private static Imp processImp(Imp imp, MediaType preferredMediaType, List errors) { - if (!isMultiFormat(imp)) { - return imp; - } - - final Imp updatedImp = switch (preferredMediaType) { - case BANNER -> imp.getBanner() != null - ? imp.toBuilder().video(null).audio(null).xNative(null).build() - : null; - case VIDEO -> imp.getVideo() != null - ? imp.toBuilder().banner(null).audio(null).xNative(null).build() - : null; - case AUDIO -> imp.getAudio() != null - ? imp.toBuilder().banner(null).video(null).xNative(null).build() - : null; - case NATIVE -> imp.getXNative() != null - ? imp.toBuilder().banner(null).video(null).audio(null).build() - : null; - }; - - if (updatedImp == null) { - errors.add(BidderError.badInput("Imp " + imp.getId() + " does not have a media type after filtering " - + "and has been removed from the request for this bidder.")); - } - return updatedImp; - } - - private static boolean isMultiFormat(Imp imp) { - int count = 0; - return (imp.getBanner() != null && ++count > 1) - || (imp.getVideo() != null && ++count > 1) - || (imp.getAudio() != null && ++count > 1) - || (imp.getXNative() != null && ++count > 1); - } -} diff --git a/src/main/java/org/prebid/server/bidder/BidderInfo.java b/src/main/java/org/prebid/server/bidder/BidderInfo.java index 1ff8f323701..d26cc2c4b2c 100644 --- a/src/main/java/org/prebid/server/bidder/BidderInfo.java +++ b/src/main/java/org/prebid/server/bidder/BidderInfo.java @@ -7,7 +7,9 @@ import org.prebid.server.spring.config.bidder.model.CompressionType; import org.prebid.server.spring.config.bidder.model.MediaType; +import java.util.HashSet; import java.util.List; +import java.util.Set; @Value(staticConstructor = "of") public class BidderInfo { @@ -28,7 +30,7 @@ public class BidderInfo { List vendors; - List currencyAccepted; + Set currencyAccepted; GdprInfo gdpr; @@ -72,7 +74,7 @@ public static BidderInfo create(boolean enabled, platformInfo(siteMediaTypes), platformInfo(doohMediaTypes)), supportedVendors, - currencyAccepted, + currencyAccepted != null ? new HashSet<>(currencyAccepted) : null, new GdprInfo(vendorId), ccpaEnforced, modifyingVastXmlAllowed, diff --git a/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java b/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java index 228d4702435..64a8dc7614a 100644 --- a/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java @@ -33,6 +33,11 @@ import org.prebid.server.auction.VideoResponseFactory; import org.prebid.server.auction.VideoStoredRequestProcessor; import org.prebid.server.auction.WinningBidComparatorFactory; +import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestCleaner; +import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestCurrencyBlocker; +import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestMediaFilter; +import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestPreferredMediaProcessor; +import org.prebid.server.auction.bidderrequestpostprocessor.CompositeBidderRequestPostProcessor; import org.prebid.server.auction.categorymapping.BasicCategoryMappingService; import org.prebid.server.auction.categorymapping.CategoryMappingService; import org.prebid.server.auction.categorymapping.NoOpCategoryMappingService; @@ -47,10 +52,6 @@ import org.prebid.server.auction.gpp.processor.GppContextProcessor; import org.prebid.server.auction.gpp.processor.tcfeuv2.TcfEuV2ContextProcessor; import org.prebid.server.auction.gpp.processor.uspv1.UspV1ContextProcessor; -import org.prebid.server.auction.mediatypeprocessor.BidderMediaTypeProcessor; -import org.prebid.server.auction.mediatypeprocessor.CompositeMediaTypeProcessor; -import org.prebid.server.auction.mediatypeprocessor.MediaTypeProcessor; -import org.prebid.server.auction.mediatypeprocessor.MultiFormatMediaTypeProcessor; import org.prebid.server.auction.privacy.contextfactory.AmpPrivacyContextFactory; import org.prebid.server.auction.privacy.contextfactory.AuctionPrivacyContextFactory; import org.prebid.server.auction.privacy.contextfactory.CookieSyncPrivacyContextFactory; @@ -145,6 +146,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Properties; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -791,20 +793,41 @@ BidderCatalog bidderCatalog(List bidderDeps) { return new BidderCatalog(bidderDeps); } + @Bean + BidderRequestCurrencyBlocker bidderRequestCurrencyBlocker(BidderCatalog bidderCatalog) { + return new BidderRequestCurrencyBlocker(bidderCatalog); + } + @Bean @ConditionalOnProperty(prefix = "auction.filter-imp-media-type", name = "enabled", havingValue = "true") - MediaTypeProcessor bidderMediaTypeProcessor(BidderCatalog bidderCatalog) { - return new BidderMediaTypeProcessor(bidderCatalog); + BidderRequestMediaFilter bidderRequestMediaFilter(BidderCatalog bidderCatalog) { + return new BidderRequestMediaFilter(bidderCatalog); } @Bean - MediaTypeProcessor multiFormatMediaTypeProcessor(BidderCatalog bidderCatalog) { - return new MultiFormatMediaTypeProcessor(bidderCatalog); + BidderRequestPreferredMediaProcessor bidderRequestPreferredMediaProcessor(BidderCatalog bidderCatalog) { + return new BidderRequestPreferredMediaProcessor(bidderCatalog); } @Bean - CompositeMediaTypeProcessor compositeMediaTypeProcessor(List mediaTypeProcessors) { - return new CompositeMediaTypeProcessor(mediaTypeProcessors); + BidderRequestCleaner bidderRequestCleaner() { + return new BidderRequestCleaner(); + } + + @Bean + CompositeBidderRequestPostProcessor compositeBidderRequestPostProcessor( + BidderRequestCurrencyBlocker bidderRequestCurrencyBlocker, + @Autowired(required = false) BidderRequestMediaFilter bidderRequestMediaFilter, + BidderRequestPreferredMediaProcessor bidderRequestPreferredMediaProcessor, + BidderRequestCleaner bidderRequestCleaner) { + + return new CompositeBidderRequestPostProcessor(Stream.of( + bidderRequestCurrencyBlocker, + bidderRequestMediaFilter, + bidderRequestPreferredMediaProcessor, + bidderRequestCleaner) + .filter(Objects::nonNull) + .toList()); } @Bean @@ -916,7 +939,7 @@ ExchangeService exchangeService( ImpAdjuster impAdjuster, SupplyChainResolver supplyChainResolver, DebugResolver debugResolver, - CompositeMediaTypeProcessor mediaTypeProcessor, + CompositeBidderRequestPostProcessor bidderRequestPostProcessor, UidUpdater uidUpdater, TimeoutResolver timeoutResolver, TimeoutFactory timeoutFactory, @@ -944,7 +967,7 @@ ExchangeService exchangeService( impAdjuster, supplyChainResolver, debugResolver, - mediaTypeProcessor, + bidderRequestPostProcessor, uidUpdater, timeoutResolver, timeoutFactory, diff --git a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java index 20561c5b685..8e5291b4c1c 100644 --- a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java +++ b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java @@ -43,18 +43,21 @@ import org.prebid.server.activity.Activity; import org.prebid.server.activity.ComponentType; import org.prebid.server.activity.infrastructure.ActivityInfrastructure; +import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestPostProcessingResult; +import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestPostProcessor; +import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestRejectedException; import org.prebid.server.auction.externalortb.StoredResponseProcessor; -import org.prebid.server.auction.mediatypeprocessor.MediaTypeProcessingResult; -import org.prebid.server.auction.mediatypeprocessor.MediaTypeProcessor; import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.AuctionParticipation; +import org.prebid.server.auction.model.BidRejectionReason; import org.prebid.server.auction.model.BidRejectionTracker; import org.prebid.server.auction.model.BidRequestCacheInfo; import org.prebid.server.auction.model.BidderPrivacyResult; import org.prebid.server.auction.model.BidderRequest; import org.prebid.server.auction.model.BidderResponse; -import org.prebid.server.auction.model.MultiBidConfig; import org.prebid.server.auction.model.ImpRejection; +import org.prebid.server.auction.model.MultiBidConfig; +import org.prebid.server.auction.model.Rejection; import org.prebid.server.auction.model.StoredResponseResult; import org.prebid.server.auction.model.TimeoutContext; import org.prebid.server.auction.model.debug.DebugContext; @@ -208,7 +211,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.prebid.server.auction.model.BidRejectionReason.NO_BID; -import static org.prebid.server.auction.model.BidRejectionReason.REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY; +import static org.prebid.server.auction.model.BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE; import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; import static org.prebid.server.proto.openrtb.ext.response.BidType.video; @@ -237,7 +240,7 @@ public class ExchangeServiceTest extends VertxTest { private DebugResolver debugResolver; @Mock(strictness = LENIENT) - private MediaTypeProcessor mediaTypeProcessor; + private BidderRequestPostProcessor bidderRequestPostProcessor; @Mock(strictness = LENIENT) private UidUpdater uidUpdater; @@ -363,8 +366,9 @@ public void setUp() { given(bidsAdjuster.validateAndAdjustBids(any(), any(), any())) .willAnswer(invocation -> invocation.getArgument(0)); - given(mediaTypeProcessor.process(any(), anyString(), any(), any())) - .willAnswer(invocation -> MediaTypeProcessingResult.succeeded(invocation.getArgument(0), emptyList())); + given(bidderRequestPostProcessor.process(any(), any(), any())) + .willAnswer(invocation -> Future.succeededFuture( + BidderRequestPostProcessingResult.withValue(invocation.getArgument(0)))); given(uidUpdater.updateUid(any(), any(), any())) .willAnswer(inv -> Optional.ofNullable((AuctionContext) inv.getArgument(1)) @@ -3923,7 +3927,7 @@ public void shouldReduceBidsHavingDealIdWithSameImpIdByBidderWithToleratingNotOb Deal.builder().id("dealId2").build())) .build())); final BidRequest bidRequest = givenBidRequest(singletonList(imp), identity()); - final AuctionContext auctionContext = givenRequestContext(bidRequest).toBuilder().build(); + final AuctionContext auctionContext = givenRequestContext(bidRequest); final BidderBid bidderBid = givenBidderBid( Bid.builder().id("bidId2").impid("impId1").dealid("dealId2").price(BigDecimal.ONE).build()); @@ -3958,7 +3962,7 @@ public void shouldReduceBidsHavingDealIdWithSameImpIdByBidderWithToleratingNotOb Deal.builder().id("dealId2").build())) .build())); final BidRequest bidRequest = givenBidRequest(singletonList(imp), identity()); - final AuctionContext auctionContext = givenRequestContext(bidRequest).toBuilder().build(); + final AuctionContext auctionContext = givenRequestContext(bidRequest); givenBidder(givenSeatBid(emptyList())); @@ -3976,75 +3980,52 @@ public void shouldReduceBidsHavingDealIdWithSameImpIdByBidderWithToleratingNotOb } @Test - public void shouldResponseWithEmptySeatBidIfBidderNotSupportProvidedMediaTypes() { + public void shouldResponseWithAddedWarningsFromBidderRequestPostProcessor() { // given final Imp imp = givenImp(singletonMap("bidder1", 1), builder -> builder.id("impId1")); final BidRequest bidRequest = givenBidRequest(singletonList(imp), identity()); - final AuctionContext auctionContext = givenRequestContext(bidRequest).toBuilder().build(); + final AuctionContext auctionContext = givenRequestContext(bidRequest); - given(mediaTypeProcessor.process(any(), anyString(), any(), any())) - .willReturn(MediaTypeProcessingResult.rejected(Collections.singletonList( - BidderError.badInput("MediaTypeProcessor error.")))); - given(bidResponseCreator.create( - argThat(argument -> argument.getAuctionParticipations().getFirst() - .getBidderResponse() - .equals(BidderResponse.of( - "bidder1", - BidderSeatBid.builder() - .warnings(Collections.singletonList( - BidderError.badInput("MediaTypeProcessor error."))) - .build(), - 0))), - any(), - any())) - .willReturn(Future.succeededFuture(BidResponse.builder().id("uniqId").build())); + given(bidderRequestPostProcessor.process(any(), any(), any())) + .willAnswer(invocation -> Future.succeededFuture(BidderRequestPostProcessingResult.of( + invocation.getArgument(0), + singletonList(BidderError.badInput("BidderRequestPostProcessor error."))))); + givenBidder(givenSeatBid(emptyList())); // when - final Future result = target.holdAuction(auctionContext); + target.holdAuction(auctionContext); // then - assertThat(result.result()) - .extracting(AuctionContext::getBidResponse) - .isEqualTo(BidResponse.builder().id("uniqId").build()); + verify(httpBidderRequester).requestBids(any(), any(), any(), any(), any(), any(), anyBoolean()); + + final ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(storedResponseProcessor).updateStoredBidResponse(captor.capture()); + assertThat(captor.getValue()) + .extracting(AuctionParticipation::getBidderResponse) + .extracting(BidderResponse::getSeatBid) + .flatExtracting(BidderSeatBid::getWarnings) + .containsExactly(BidderError.badInput("BidderRequestPostProcessor error.")); } @Test - public void shouldResponseWithEmptySeatBidIfBidderNotSupportRequestCurrency() { + public void shouldResponseWithEmptySeatBidIfRejectedByBidderRequestPostProcessor() { // given final Imp imp = givenImp(singletonMap("bidder1", 1), builder -> builder.id("impId1")); - final BidRequest bidRequest = givenBidRequest(singletonList(imp), - bidRequestBuilder -> bidRequestBuilder.cur(singletonList("USD"))); + final BidRequest bidRequest = givenBidRequest(singletonList(imp), identity()); final AuctionContext auctionContext = givenRequestContext(bidRequest); - given(bidderCatalog.bidderInfoByName(anyString())).willReturn(BidderInfo.create( - true, - null, - false, - null, - null, - null, - null, - null, - null, - null, - 0, - singletonList("CAD"), - false, - false, - CompressionType.NONE, - Ortb.of(false), - 0L)); - + given(bidderRequestPostProcessor.process(any(), any(), any())) + .willReturn(Future.failedFuture(new BidderRequestRejectedException( + BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE, + singletonList(BidderError.badInput("BidderRequestPostProcessor error."))))); given(bidResponseCreator.create( argThat(argument -> argument.getAuctionParticipations().getFirst() .getBidderResponse() .equals(BidderResponse.of( "bidder1", BidderSeatBid.builder() - .warnings(Collections.singletonList( - BidderError.generic( - "No match between the configured currencies and bidRequest.cur" - ))) + .warnings(singletonList( + BidderError.badInput("BidderRequestPostProcessor error."))) .build(), 0))), any(), @@ -4055,14 +4036,17 @@ public void shouldResponseWithEmptySeatBidIfBidderNotSupportRequestCurrency() { final Future result = target.holdAuction(auctionContext); // then + verifyNoInteractions(httpBidderRequester); assertThat(result.result()) .extracting(AuctionContext::getBidResponse) .isEqualTo(BidResponse.builder().id("uniqId").build()); assertThat(result.result()) .extracting(AuctionContext::getBidRejectionTrackers) - .extracting(rejectionTrackers -> rejectionTrackers.get("bidder1")) - .extracting(BidRejectionTracker::getRejected) - .isEqualTo(Set.of(ImpRejection.of("bidder1", "impId1", REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY))); + .extracting(trackers -> trackers.get("bidder1")) + .extracting(BidRejectionTracker::getAllRejected) + .extracting(rejections -> rejections.get("impId1")) + .asInstanceOf(InstanceOfAssertFactories.list(Rejection.class)) + .containsExactly(ImpRejection.of("bidder1", "impId1", REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE)); } @Test @@ -4249,7 +4233,7 @@ private void givenTarget(boolean enabledStrictAppSiteDoohValidation) { fpdResolver, impAdjuster, supplyChainResolver, debugResolver, - mediaTypeProcessor, + bidderRequestPostProcessor, uidUpdater, timeoutResolver, timeoutFactory, diff --git a/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleanerTest.java b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleanerTest.java new file mode 100644 index 00000000000..62e50c146bf --- /dev/null +++ b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleanerTest.java @@ -0,0 +1,69 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.BidRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.prebid.server.VertxTest; +import org.prebid.server.auction.model.BidderRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +public class BidderRequestCleanerTest extends VertxTest { + + private static final String BIDDER = "bidder"; + + private BidderRequestCleaner target; + + @BeforeEach + public void setUp() { + target = new BidderRequestCleaner(); + } + + @Test + public void processShouldReturnSameRequest() { + // given + final BidderRequest bidderRequest = givenBidderRequest(null); + + // when + final BidderRequestPostProcessingResult result = target.process(bidderRequest, null, null).result(); + + // then + assertThat(result.getValue()).isSameAs(bidderRequest); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldReturnCleanedRequest() { + // given + final BidderRequest bidderRequest = givenBidderRequest(mapper.createObjectNode()); + + // when + final BidderRequestPostProcessingResult result = target.process(bidderRequest, null, null).result(); + + // then + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) + .extracting(BidRequest::getExt) + .extracting(ExtRequest::getPrebid) + .extracting(ExtRequestPrebid::getBiddercontrols) + .isNull(); + assertThat(result.getErrors()).isEmpty(); + } + + private static BidderRequest givenBidderRequest(ObjectNode bidderControls) { + return BidderRequest.builder() + .bidRequest(BidRequest.builder() + .ext(ExtRequest.of(ExtRequestPrebid.builder() + .biddercontrols(bidderControls) + .build())) + .build()) + .bidder(BIDDER) + .build(); + } +} diff --git a/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCurrencyBlockerTest.java b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCurrencyBlockerTest.java new file mode 100644 index 00000000000..548a9e9c4b8 --- /dev/null +++ b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCurrencyBlockerTest.java @@ -0,0 +1,165 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import com.iab.openrtb.request.BidRequest; +import io.vertx.core.Future; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.BidRejectionReason; +import org.prebid.server.auction.model.BidderRequest; +import org.prebid.server.auction.versionconverter.OrtbVersion; +import org.prebid.server.bidder.BidderCatalog; +import org.prebid.server.bidder.BidderInfo; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.spring.config.bidder.model.CompressionType; +import org.prebid.server.spring.config.bidder.model.Ortb; + +import java.util.List; + +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class BidderRequestCurrencyBlockerTest { + + private static final String BIDDER = "bidder"; + + @Mock + private BidderCatalog bidderCatalog; + @Mock + private BidderAliases bidderAliases; + + private BidderRequestCurrencyBlocker target; + + @BeforeEach + public void setUp() { + target = new BidderRequestCurrencyBlocker(bidderCatalog); + when(bidderAliases.resolveBidder(anyString())).thenAnswer(invocation -> invocation.getArgument(0)); + } + + @Test + public void processShouldReturnSameRequestIfAcceptedCurrenciesAreNotConfigured() { + // given + given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(null)); + final BidderRequest bidderRequest = givenBidderRequest(List.of("USD")); + + // when + final BidderRequestPostProcessingResult result = target.process(bidderRequest, bidderAliases, null).result(); + + // then + assertThat(result.getValue()).isSameAs(bidderRequest); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldReturnSameRequestIfAcceptedCurrenciesAreEmpty() { + // given + given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(emptyList())); + final BidderRequest bidderRequest = givenBidderRequest(List.of("USD")); + + // when + final BidderRequestPostProcessingResult result = target.process(bidderRequest, bidderAliases, null).result(); + + // then + assertThat(result.getValue()).isSameAs(bidderRequest); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldReturnSameRequestIfRequestCurrencyIsNull() { + // given + given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(List.of("USD"))); + final BidderRequest bidderRequest = givenBidderRequest(null); + + // when + final BidderRequestPostProcessingResult result = target.process(bidderRequest, bidderAliases, null).result(); + + // then + assertThat(result.getValue()).isSameAs(bidderRequest); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldReturnSameRequestIfRequestCurrencyIsEmpty() { + // given + given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(List.of("USD"))); + final BidderRequest bidderRequest = givenBidderRequest(emptyList()); + + // when + final BidderRequestPostProcessingResult result = target.process(bidderRequest, bidderAliases, null).result(); + + // then + assertThat(result.getValue()).isSameAs(bidderRequest); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldReturnSameRequestIfRequestCurrencyContainsAcceptedCurrency() { + // given + given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(List.of("EUR", "USD"))); + final BidderRequest bidderRequest = givenBidderRequest(List.of("UAH", "USD")); + + // when + final BidderRequestPostProcessingResult result = target.process(bidderRequest, bidderAliases, null).result(); + + // then + assertThat(result.getValue()).isSameAs(bidderRequest); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void processShouldReturnFailureIfRequestDoesNotContainsAcceptedCurrency() { + // given + given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(List.of("EUR", "USD"))); + final BidderRequest bidderRequest = givenBidderRequest(List.of("UAH")); + + // when + final Future result = target.process(bidderRequest, bidderAliases, null); + + // then + assertThat(result.failed()).isTrue(); + assertThat(result.cause()) + .asInstanceOf(InstanceOfAssertFactories.type(BidderRequestRejectedException.class)) + .satisfies(e -> { + assertThat(e.getRejectionReason()) + .isEqualTo(BidRejectionReason.REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY); + assertThat(e.getErrors()).containsExactly( + BidderError.generic("No match between the configured currencies and bidRequest.cur")); + }); + } + + private static BidderInfo givenBidderInfo(List currencies) { + return BidderInfo.create( + true, + OrtbVersion.ORTB_2_6, + false, + "endpoint", + null, + "maintainerEmail", + null, + null, + null, + emptyList(), + 0, + currencies, + false, + false, + CompressionType.NONE, + Ortb.of(false), + 0L); + } + + private static BidderRequest givenBidderRequest(List currencies) { + return BidderRequest.builder() + .bidRequest(BidRequest.builder().cur(currencies).build()) + .bidder(BIDDER) + .build(); + } +} diff --git a/src/test/java/org/prebid/server/auction/mediatypeprocessor/BidderMediaTypeProcessorTest.java b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestMediaFilterTest.java similarity index 61% rename from src/test/java/org/prebid/server/auction/mediatypeprocessor/BidderMediaTypeProcessorTest.java rename to src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestMediaFilterTest.java index d6e002489f8..9d342237370 100644 --- a/src/test/java/org/prebid/server/auction/mediatypeprocessor/BidderMediaTypeProcessorTest.java +++ b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestMediaFilterTest.java @@ -1,4 +1,4 @@ -package org.prebid.server.auction.mediatypeprocessor; +package org.prebid.server.auction.bidderrequestpostprocessor; import com.iab.openrtb.request.App; import com.iab.openrtb.request.Audio; @@ -8,6 +8,8 @@ import com.iab.openrtb.request.Native; import com.iab.openrtb.request.Site; import com.iab.openrtb.request.Video; +import io.vertx.core.Future; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -15,6 +17,8 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.prebid.server.VertxTest; import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.BidRejectionReason; +import org.prebid.server.auction.model.BidderRequest; import org.prebid.server.auction.versionconverter.OrtbVersion; import org.prebid.server.bidder.BidderCatalog; import org.prebid.server.bidder.BidderInfo; @@ -41,7 +45,7 @@ import static org.prebid.server.spring.config.bidder.model.MediaType.VIDEO; @ExtendWith(MockitoExtension.class) -public class BidderMediaTypeProcessorTest extends VertxTest { +public class BidderRequestMediaFilterTest extends VertxTest { private static final String BIDDER = "bidder"; @@ -50,42 +54,50 @@ public class BidderMediaTypeProcessorTest extends VertxTest { @Mock private BidderAliases bidderAliases; - private BidderMediaTypeProcessor target; + private BidderRequestMediaFilter target; @BeforeEach public void setUp() { - target = new BidderMediaTypeProcessor(bidderCatalog); + target = new BidderRequestMediaFilter(bidderCatalog); when(bidderAliases.resolveBidder(anyString())).thenAnswer(invocation -> invocation.getArgument(0)); } @Test - public void processShouldReturnRejectedResultAndErrorIfBidderNotSupportAnyMediaType() { + public void processShouldReturnRejectedResultAndErrorIfBidderDoesNotSupportAnyMediaType() { // given given(bidderCatalog.bidderInfoByName(BIDDER)) .willReturn(givenBidderInfo(emptyList(), emptyList(), emptyList())); - final BidRequest bidRequest = givenBidRequest(identity(), givenImp(BANNER)); + final BidderRequest bidderRequest = givenBidderRequest(identity(), givenImp(BANNER)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, null); + final Future result = target.process(bidderRequest, bidderAliases, null); // then - assertThat(result.isRejected()).isTrue(); - assertThat(result.getErrors()).containsExactly( - BidderError.badInput("Bidder does not support any media types.")); + assertThat(result.failed()).isTrue(); + assertThat(result.cause()) + .asInstanceOf(InstanceOfAssertFactories.type(BidderRequestRejectedException.class)) + .satisfies(e -> { + assertThat(e.getRejectionReason()) + .isEqualTo(BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE); + assertThat(e.getErrors()).containsExactly( + BidderError.badInput("Bidder does not support any media types.")); + }); } @Test public void processShouldUseAppMediaTypesIfAppPresent() { // given - final BidRequest bidRequest = givenBidRequest(request -> request.app(App.builder().build()), givenImp(BANNER)); given(bidderCatalog.bidderInfoByName(BIDDER)) .willReturn(givenBidderInfo(singletonList(BANNER), singletonList(AUDIO), singletonList(NATIVE))); + final BidderRequest bidderRequest = givenBidderRequest( + request -> request.app(App.builder().build()), + givenImp(BANNER)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, null); + final BidderRequestPostProcessingResult result = target.process(bidderRequest, bidderAliases, null).result(); // then - assertThat(result.getBidRequest()).isEqualTo(bidRequest); + assertThat(result.getValue()).isEqualTo(bidderRequest); assertThat(result.getErrors()).isEmpty(); } @@ -94,18 +106,19 @@ public void processShouldRemoveUnsupportedMediaTypes() { // given given(bidderCatalog.bidderInfoByName(BIDDER)) .willReturn(givenBidderInfo(emptyList(), List.of(BANNER, AUDIO), emptyList())); - final BidRequest bidRequest = givenBidRequest( + final BidderRequest bidderRequest = givenBidderRequest( request -> request.site(Site.builder().build()), givenImp(AUDIO, NATIVE), givenImp(BANNER, VIDEO)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, null); + final BidderRequestPostProcessingResult result = target.process(bidderRequest, bidderAliases, null).result(); // then - assertThat(result.getBidRequest()) + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) .extracting(BidRequest::getImp) - .asList() + .asInstanceOf(InstanceOfAssertFactories.LIST) .containsExactly(givenImp(AUDIO), givenImp(BANNER)); assertThat(result.getErrors()).isEmpty(); } @@ -115,18 +128,19 @@ public void processShouldRemoveImpWithOnlyUnsupportedMediaTypes() { // given given(bidderCatalog.bidderInfoByName(BIDDER)) .willReturn(givenBidderInfo(emptyList(), asList(BANNER, AUDIO), emptyList())); - final BidRequest bidRequest = givenBidRequest( + final BidderRequest bidderRequest = givenBidderRequest( request -> request.site(Site.builder().build()), givenImp(VIDEO, NATIVE), givenImp(BANNER, AUDIO)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, null); + final BidderRequestPostProcessingResult result = target.process(bidderRequest, bidderAliases, null).result(); // then - assertThat(result.getBidRequest()) + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) .extracting(BidRequest::getImp) - .asList() + .asInstanceOf(InstanceOfAssertFactories.LIST) .containsExactly(givenImp(BANNER, AUDIO)); assertThat(result.getErrors()).containsExactly( BidderError.badInput("Imp " + null + " does not have a supported media type " @@ -138,22 +152,28 @@ public void processShouldReturnRejectedResultIfRequestDoesNotContainsAnyImpWithS // given given(bidderCatalog.bidderInfoByName(BIDDER)) .willReturn(givenBidderInfo(emptyList(), asList(BANNER, AUDIO), emptyList())); - final BidRequest bidRequest = givenBidRequest( + final BidderRequest bidderRequest = givenBidderRequest( request -> request.site(Site.builder().build()), givenImp(VIDEO), givenImp(NATIVE)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, null); + final Future result = target.process(bidderRequest, bidderAliases, null); // then - assertThat(result.isRejected()).isTrue(); - assertThat(result.getErrors()).containsExactly( - BidderError.badInput("Imp " + null + " does not have a supported media type " - + "and has been removed from the request for this bidder."), - BidderError.badInput("Imp " + null + " does not have a supported media type " - + "and has been removed from the request for this bidder."), - BidderError.badInput("Bid request contains 0 impressions after filtering.")); + assertThat(result.failed()).isTrue(); + assertThat(result.cause()) + .asInstanceOf(InstanceOfAssertFactories.type(BidderRequestRejectedException.class)) + .satisfies(e -> { + assertThat(e.getRejectionReason()) + .isEqualTo(BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE); + assertThat(e.getErrors()).containsExactly( + BidderError.badInput("Imp null does not have a supported media type" + + " and has been removed from the request for this bidder."), + BidderError.badInput("Imp null does not have a supported media type" + + " and has been removed from the request for this bidder."), + BidderError.badInput("Bid request contains 0 impressions after filtering.")); + }); } private static BidderInfo givenBidderInfo(List appMediaTypes, @@ -179,10 +199,13 @@ private static BidderInfo givenBidderInfo(List appMediaTypes, 0L); } - private static BidRequest givenBidRequest(UnaryOperator bidRequestCustomizer, - Imp... imps) { + private static BidderRequest givenBidderRequest(UnaryOperator bidRequestCustomizer, + Imp... imps) { - return bidRequestCustomizer.apply(BidRequest.builder()).imp(asList(imps)).build(); + return BidderRequest.builder() + .bidRequest(bidRequestCustomizer.apply(BidRequest.builder()).imp(asList(imps)).build()) + .bidder(BIDDER) + .build(); } private static Imp givenImp(MediaType... mediaTypes) { diff --git a/src/test/java/org/prebid/server/auction/mediatypeprocessor/MultiFormatMediaTypeProcessorTest.java b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPreferredMediaProcessorTest.java similarity index 64% rename from src/test/java/org/prebid/server/auction/mediatypeprocessor/MultiFormatMediaTypeProcessorTest.java rename to src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPreferredMediaProcessorTest.java index a8da73fe320..74f00ca11a4 100644 --- a/src/test/java/org/prebid/server/auction/mediatypeprocessor/MultiFormatMediaTypeProcessorTest.java +++ b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPreferredMediaProcessorTest.java @@ -1,4 +1,4 @@ -package org.prebid.server.auction.mediatypeprocessor; +package org.prebid.server.auction.bidderrequestpostprocessor; import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.Audio; @@ -7,6 +7,7 @@ import com.iab.openrtb.request.Imp; import com.iab.openrtb.request.Native; import com.iab.openrtb.request.Video; +import io.vertx.core.Future; import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -15,6 +16,9 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.prebid.server.VertxTest; import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.auction.model.BidRejectionReason; +import org.prebid.server.auction.model.BidderRequest; import org.prebid.server.auction.versionconverter.OrtbVersion; import org.prebid.server.bidder.BidderCatalog; import org.prebid.server.bidder.BidderInfo; @@ -44,7 +48,7 @@ import static org.prebid.server.spring.config.bidder.model.MediaType.VIDEO; @ExtendWith(MockitoExtension.class) -public class MultiFormatMediaTypeProcessorTest extends VertxTest { +public class BidderRequestPreferredMediaProcessorTest extends VertxTest { private static final String BIDDER = "bidder"; @@ -53,11 +57,11 @@ public class MultiFormatMediaTypeProcessorTest extends VertxTest { @Mock private BidderAliases bidderAliases; - private MultiFormatMediaTypeProcessor target; + private BidderRequestPreferredMediaProcessor target; @BeforeEach public void setUp() { - target = new MultiFormatMediaTypeProcessor(bidderCatalog); + target = new BidderRequestPreferredMediaProcessor(bidderCatalog); when(bidderAliases.resolveBidder(anyString())).thenAnswer(invocation -> invocation.getArgument(0)); } @@ -65,14 +69,13 @@ public void setUp() { public void processShouldReturnSameBidRequestIfMultiFormatSupported() { // given given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(true)); - final BidRequest bidRequest = givenBidRequest(identity(), givenImp(BANNER, VIDEO)); + final BidderRequest bidderRequest = givenBidderRequest(identity(), givenImp(BANNER, VIDEO)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, null); + final BidderRequestPostProcessingResult result = target.process(bidderRequest, bidderAliases, null).result(); // then - assertThat(result.isRejected()).isFalse(); - assertThat(result.getBidRequest()).isEqualTo(bidRequest); + assertThat(result.getValue()).isEqualTo(bidderRequest); assertThat(result.getErrors()).isEmpty(); } @@ -80,15 +83,15 @@ public void processShouldReturnSameBidRequestIfMultiFormatSupported() { public void processShouldReturnSameBidRequestIfPreferredMediaTypeNotSpecified() { // given given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(false)); - final BidRequest bidRequest = givenBidRequest(identity(), givenImp(BANNER, VIDEO)); - final Account account = givenAccount(null); + final BidderRequest bidderRequest = givenBidderRequest(identity(), givenImp(BANNER, VIDEO)); + final AuctionContext auctionContext = givenAuctionContext(null); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, account); + final BidderRequestPostProcessingResult result = + target.process(bidderRequest, bidderAliases, auctionContext).result(); // then - assertThat(result.isRejected()).isFalse(); - assertThat(result.getBidRequest()).isEqualTo(bidRequest); + assertThat(result.getValue()).isEqualTo(bidderRequest); assertThat(result.getErrors()).isEmpty(); } @@ -96,15 +99,16 @@ public void processShouldReturnSameBidRequestIfPreferredMediaTypeNotSpecified() public void processShouldReturnImpWithPreferredMediaType() { // given given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(false)); - final BidRequest bidRequest = givenBidRequest(identity(), givenImp(BANNER, VIDEO, AUDIO, NATIVE)); - final Account account = givenAccount(Map.of(BIDDER, VIDEO)); + final BidderRequest bidderRequest = givenBidderRequest(identity(), givenImp(BANNER, VIDEO, AUDIO, NATIVE)); + final AuctionContext auctionContext = givenAuctionContext(Map.of(BIDDER, VIDEO)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, account); + final BidderRequestPostProcessingResult result = + target.process(bidderRequest, bidderAliases, auctionContext).result(); // then - assertThat(result.isRejected()).isFalse(); - assertThat(result.getBidRequest()) + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) .extracting(BidRequest::getImp) .asInstanceOf(InstanceOfAssertFactories.list(Imp.class)) .containsExactly(givenImp(VIDEO)); @@ -120,28 +124,24 @@ public void processShouldUseRequestLevelPreferredMediaTypeFirst() { final ObjectNode bidderControls = mapper.createObjectNode(); bidderControls.putObject(BIDDER).put("prefmtype", "video"); - final BidRequest bidRequest = givenBidRequest( + final BidderRequest bidderRequest = givenBidderRequest( request -> request.ext(ExtRequest.of(ExtRequestPrebid.builder() .biddercontrols(bidderControls) .build())), givenImp(BANNER, VIDEO, AUDIO, NATIVE)); - final Account account = givenAccount(Map.of("resolvedBidderName", AUDIO)); + final AuctionContext auctionContext = givenAuctionContext(Map.of("resolvedBidderName", AUDIO)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, account); + final BidderRequestPostProcessingResult result = + target.process(bidderRequest, bidderAliases, auctionContext).result(); // then - assertThat(result.isRejected()).isFalse(); - assertThat(result.getBidRequest()) + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) .extracting(BidRequest::getImp) .asInstanceOf(InstanceOfAssertFactories.list(Imp.class)) .containsExactly(givenImp(VIDEO)); - assertThat(result.getBidRequest()) - .extracting(BidRequest::getExt) - .extracting(ExtRequest::getPrebid) - .extracting(ExtRequestPrebid::getBiddercontrols) - .isNull(); assertThat(result.getErrors()).isEmpty(); } @@ -154,28 +154,24 @@ public void processShouldUseRequestLevelPreferredMediaTypeFirstCaseInsensitive() final ObjectNode bidderControls = mapper.createObjectNode(); bidderControls.putObject(BIDDER.toUpperCase()).put("prefmtype", "video"); - final BidRequest bidRequest = givenBidRequest( + final BidderRequest bidderRequest = givenBidderRequest( request -> request.ext(ExtRequest.of(ExtRequestPrebid.builder() .biddercontrols(bidderControls) .build())), givenImp(BANNER, VIDEO, AUDIO, NATIVE)); - final Account account = givenAccount(Map.of("resolvedBidderName", AUDIO)); + final AuctionContext auctionContext = givenAuctionContext(Map.of("resolvedBidderName", AUDIO)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, account); + final BidderRequestPostProcessingResult result = + target.process(bidderRequest, bidderAliases, auctionContext).result(); // then - assertThat(result.isRejected()).isFalse(); - assertThat(result.getBidRequest()) + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) .extracting(BidRequest::getImp) .asInstanceOf(InstanceOfAssertFactories.list(Imp.class)) .containsExactly(givenImp(VIDEO)); - assertThat(result.getBidRequest()) - .extracting(BidRequest::getExt) - .extracting(ExtRequest::getPrebid) - .extracting(ExtRequestPrebid::getBiddercontrols) - .isNull(); assertThat(result.getErrors()).isEmpty(); } @@ -183,21 +179,22 @@ public void processShouldUseRequestLevelPreferredMediaTypeFirstCaseInsensitive() public void processShouldSkipImpsWithSingleMediaType() { // given given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(false)); - final BidRequest bidRequest = givenBidRequest( + final BidderRequest bidderRequest = givenBidderRequest( identity(), givenImp(BANNER), givenImp(VIDEO), givenImp(BANNER, VIDEO, AUDIO, NATIVE), givenImp(AUDIO), givenImp(NATIVE)); - final Account account = givenAccount(Map.of(BIDDER, VIDEO)); + final AuctionContext auctionContext = givenAuctionContext(Map.of(BIDDER, VIDEO)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, account); + final BidderRequestPostProcessingResult result = + target.process(bidderRequest, bidderAliases, auctionContext).result(); // then - assertThat(result.isRejected()).isFalse(); - assertThat(result.getBidRequest()) + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) .extracting(BidRequest::getImp) .asInstanceOf(InstanceOfAssertFactories.list(Imp.class)) .containsExactly( @@ -213,7 +210,7 @@ public void processShouldSkipImpsWithSingleMediaType() { public void processShouldFilterMultiFormatImpsWithoutPreferredMediaType() { // given given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(false)); - final BidRequest bidRequest = givenBidRequest( + final BidderRequest bidderRequest = givenBidderRequest( identity(), givenImp(BANNER, AUDIO), givenImp(BANNER, VIDEO), @@ -221,14 +218,15 @@ public void processShouldFilterMultiFormatImpsWithoutPreferredMediaType() { givenImp(VIDEO, AUDIO), givenImp(VIDEO, NATIVE), givenImp(AUDIO, NATIVE)); - final Account account = givenAccount(Map.of(BIDDER, VIDEO)); + final AuctionContext auctionContext = givenAuctionContext(Map.of(BIDDER, VIDEO)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, account); + final BidderRequestPostProcessingResult result = + target.process(bidderRequest, bidderAliases, auctionContext).result(); // then - assertThat(result.isRejected()).isFalse(); - assertThat(result.getBidRequest()) + assertThat(result.getValue()) + .extracting(BidderRequest::getBidRequest) .extracting(BidRequest::getImp) .asInstanceOf(InstanceOfAssertFactories.list(Imp.class)) .containsExactly( @@ -248,18 +246,25 @@ public void processShouldFilterMultiFormatImpsWithoutPreferredMediaType() { public void processShouldRejectEmptyRequestAfterFiltering() { // given given(bidderCatalog.bidderInfoByName(BIDDER)).willReturn(givenBidderInfo(false)); - final BidRequest bidRequest = givenBidRequest(identity(), givenImp(BANNER, AUDIO, NATIVE)); - final Account account = givenAccount(Map.of(BIDDER, VIDEO)); + final BidderRequest bidderRequest = givenBidderRequest(identity(), givenImp(BANNER, AUDIO, NATIVE)); + final AuctionContext auctionContext = givenAuctionContext(Map.of(BIDDER, VIDEO)); // when - final MediaTypeProcessingResult result = target.process(bidRequest, BIDDER, bidderAliases, account); + final Future result = + target.process(bidderRequest, bidderAliases, auctionContext); // then - assertThat(result.isRejected()).isTrue(); - assertThat(result.getErrors()).containsExactly( - BidderError.badInput("Imp null does not have a media type after filtering" - + " and has been removed from the request for this bidder."), - BidderError.badInput("Bid request contains 0 impressions after filtering.")); + assertThat(result.failed()).isTrue(); + assertThat(result.cause()) + .asInstanceOf(InstanceOfAssertFactories.type(BidderRequestRejectedException.class)) + .satisfies(e -> { + assertThat(e.getRejectionReason()) + .isEqualTo(BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE); + assertThat(e.getErrors()).containsExactly( + BidderError.badInput("Imp null does not have a media type after filtering" + + " and has been removed from the request for this bidder."), + BidderError.badInput("Bid request contains 0 impressions after filtering.")); + }); } private static BidderInfo givenBidderInfo(boolean multiFormatSupported) { @@ -283,10 +288,13 @@ private static BidderInfo givenBidderInfo(boolean multiFormatSupported) { 0L); } - private static BidRequest givenBidRequest(UnaryOperator bidRequestCustomizer, - Imp... imps) { + private static BidderRequest givenBidderRequest(UnaryOperator bidRequestCustomizer, + Imp... imps) { - return bidRequestCustomizer.apply(BidRequest.builder()).imp(asList(imps)).build(); + return BidderRequest.builder() + .bidder(BIDDER) + .bidRequest(bidRequestCustomizer.apply(BidRequest.builder()).imp(asList(imps)).build()) + .build(); } private static Imp givenImp(MediaType... mediaTypes) { @@ -309,10 +317,12 @@ private static Imp givenImp(MediaType... mediaTypes) { return impBuilder.build(); } - private static Account givenAccount(Map bidderToPreferredMediaType) { - return Account.builder() - .auction(AccountAuctionConfig.builder() - .preferredMediaTypes(bidderToPreferredMediaType) + private static AuctionContext givenAuctionContext(Map bidderToPreferredMediaType) { + return AuctionContext.builder() + .account(Account.builder() + .auction(AccountAuctionConfig.builder() + .preferredMediaTypes(bidderToPreferredMediaType) + .build()) .build()) .build(); } diff --git a/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/CompositeBidderRequestPostProcessorTest.java b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/CompositeBidderRequestPostProcessorTest.java new file mode 100644 index 00000000000..5e7e9a52c51 --- /dev/null +++ b/src/test/java/org/prebid/server/auction/bidderrequestpostprocessor/CompositeBidderRequestPostProcessorTest.java @@ -0,0 +1,88 @@ +package org.prebid.server.auction.bidderrequestpostprocessor; + +import io.vertx.core.Future; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.prebid.server.auction.aliases.BidderAliases; +import org.prebid.server.auction.model.BidderRequest; +import org.prebid.server.bidder.model.BidderError; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mock.Strictness.LENIENT; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class CompositeBidderRequestPostProcessorTest { + + @Mock + private BidderRequestPostProcessor bidderRequestPostProcessor1; + + @Mock + private BidderRequestPostProcessor bidderRequestPostProcessor2; + + @Mock(strictness = LENIENT) + private BidderAliases bidderAliases; + + private CompositeBidderRequestPostProcessor target; + + @BeforeEach + public void setUp() { + target = new CompositeBidderRequestPostProcessor(asList( + bidderRequestPostProcessor1, bidderRequestPostProcessor2)); + when(bidderAliases.resolveBidder(anyString())).thenAnswer(invocation -> invocation.getArgument(0)); + } + + @Test + public void processShouldReturnExpectedResult() { + // given + given(bidderRequestPostProcessor1.process(any(), any(), any())) + .willReturn(Future.succeededFuture(BidderRequestPostProcessingResult.of( + BidderRequest.builder().bidder("processed by bidderRequestPostProcessor1").build(), + singletonList(BidderError.badInput("Error from bidderRequestPostProcessor1"))))); + + given(bidderRequestPostProcessor2.process( + argThat(request -> "processed by bidderRequestPostProcessor1".equals(request.getBidder())), + any(), + any())) + .willReturn(Future.succeededFuture(BidderRequestPostProcessingResult.of( + BidderRequest.builder().bidder("processed by bidderRequestPostProcessor2").build(), + singletonList(BidderError.badInput("Error from bidderRequestPostProcessor2"))))); + + // when + final Future result = target.process(null, bidderAliases, null); + + // then + assertThat(result.succeeded()).isTrue(); + assertThat(result.result().getValue()) + .isEqualTo(BidderRequest.builder().bidder("processed by bidderRequestPostProcessor2").build()); + assertThat(result.result().getErrors()) + .containsExactly( + BidderError.badInput("Error from bidderRequestPostProcessor1"), + BidderError.badInput("Error from bidderRequestPostProcessor2")); + } + + @Test + public void processShouldReturnExpectedResultIfSomeOfProcessorsFails() { + // given + given(bidderRequestPostProcessor1.process(any(), any(), any())) + .willReturn(Future.failedFuture("Error from bidderRequestPostProcessor1")); + + // when + final Future result = target.process(null, bidderAliases, null); + + // then + assertThat(result.failed()).isTrue(); + assertThat(result.cause().getMessage()).isEqualTo("Error from bidderRequestPostProcessor1"); + verifyNoInteractions(bidderRequestPostProcessor2); + } +} diff --git a/src/test/java/org/prebid/server/auction/mediatypeprocessor/CompositeMediaTypeProcessorTest.java b/src/test/java/org/prebid/server/auction/mediatypeprocessor/CompositeMediaTypeProcessorTest.java deleted file mode 100644 index 47c40634788..00000000000 --- a/src/test/java/org/prebid/server/auction/mediatypeprocessor/CompositeMediaTypeProcessorTest.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.prebid.server.auction.mediatypeprocessor; - -import com.iab.openrtb.request.BidRequest; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.prebid.server.auction.aliases.BidderAliases; -import org.prebid.server.bidder.model.BidderError; - -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mock.Strictness.LENIENT; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -public class CompositeMediaTypeProcessorTest { - - @Mock - private MediaTypeProcessor mediaTypeProcessor1; - - @Mock - private MediaTypeProcessor mediaTypeProcessor2; - - @Mock(strictness = LENIENT) - private BidderAliases bidderAliases; - - private CompositeMediaTypeProcessor target; - - @BeforeEach - public void setUp() { - target = new CompositeMediaTypeProcessor(asList(mediaTypeProcessor1, mediaTypeProcessor2)); - when(bidderAliases.resolveBidder(anyString())).thenAnswer(invocation -> invocation.getArgument(0)); - } - - @Test - public void processShouldReturnExpectedResult() { - // given - given(mediaTypeProcessor1.process(any(), anyString(), any(), any())) - .willReturn(MediaTypeProcessingResult.succeeded( - BidRequest.builder().id("processed by mediaTypeProcessor1").build(), - singletonList(BidderError.badInput("Error from mediaTypeProcessor1")))); - - given(mediaTypeProcessor2.process( - argThat(request -> "processed by mediaTypeProcessor1".equals(request.getId())), - anyString(), - any(), - any())) - .willReturn(MediaTypeProcessingResult.succeeded( - BidRequest.builder().id("processed by mediaTypeProcessor2").build(), - singletonList(BidderError.badInput("Error from mediaTypeProcessor2")))); - - // when - final MediaTypeProcessingResult result = target.process( - BidRequest.builder().build(), - "bidder", - bidderAliases, - null); - - // then - assertThat(result.isRejected()).isFalse(); - assertThat(result.getBidRequest()) - .isEqualTo(BidRequest.builder().id("processed by mediaTypeProcessor2").build()); - assertThat(result.getErrors()) - .containsExactly( - BidderError.badInput("Error from mediaTypeProcessor1"), - BidderError.badInput("Error from mediaTypeProcessor2")); - } - - @Test - public void processShouldReturnExpectedResultIfRejectedBySomeOfProcessors() { - // given - given(mediaTypeProcessor1.process(any(), anyString(), any(), any())) - .willReturn(MediaTypeProcessingResult.rejected( - singletonList(BidderError.badInput("Error from mediaTypeProcessor1")))); - - // when - final MediaTypeProcessingResult result = target.process( - BidRequest.builder().build(), - "bidder", - bidderAliases, - null); - - // then - assertThat(result.isRejected()).isTrue(); - assertThat(result.getErrors()).containsExactly(BidderError.badInput("Error from mediaTypeProcessor1")); - verifyNoInteractions(mediaTypeProcessor2); - } -} From b6073ec017599d0751e96ccbd4ea1f981382e558 Mon Sep 17 00:00:00 2001 From: Oleksandr Zhevedenko <720803+Net-burst@users.noreply.github.com> Date: Thu, 15 Jan 2026 21:44:36 -0500 Subject: [PATCH 12/38] CI: Update GitHub workflows (#4351) --- .github/workflows/code-path-changes.yml | 12 +-- .github/workflows/codeql-analysis.yml | 82 +++++++++++-------- .github/workflows/cross-repo-issue.yml | 9 +- .github/workflows/docker-image-publish.yml | 19 +++-- .github/workflows/issue_prioritization.yml | 2 + .github/workflows/pr-functional-tests.yml | 11 ++- .github/workflows/pr-java-ci.yml | 18 ++-- .../workflows/pr-module-functional-tests.yml | 11 ++- .github/workflows/release-asset-publish.yml | 6 +- .github/workflows/release-drafter.yml | 9 +- .github/workflows/slack-stale-pr.yml | 27 ------ .github/workflows/trivy-security-check.yml | 18 ++-- 12 files changed, 117 insertions(+), 107 deletions(-) delete mode 100644 .github/workflows/slack-stale-pr.yml diff --git a/.github/workflows/code-path-changes.yml b/.github/workflows/code-path-changes.yml index 23e0c19db6d..f818d867441 100644 --- a/.github/workflows/code-path-changes.yml +++ b/.github/workflows/code-path-changes.yml @@ -2,10 +2,13 @@ name: Notify Code Path Changes on: pull_request_target: - types: [opened, synchronize] + types: [ opened, synchronize ] paths: - '**' +permissions: + contents: read + env: OAUTH2_CLIENT_ID: ${{ secrets.OAUTH2_CLIENT_ID }} OAUTH2_CLIENT_SECRET: ${{ secrets.OAUTH2_CLIENT_SECRET }} @@ -14,18 +17,15 @@ env: GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -permissions: - contents: read - jobs: notify: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Set up Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v6 with: node-version: '18' diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a86f0b144a5..a6852ae7c92 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,48 +1,60 @@ -name: "CodeQL" +name: CodeQL on: - push: - branches: [ "master" ] pull_request: - branches: [ "master" ] + branches: [ 'master' ] + schedule: + - cron: '0 3 * * 1' + +permissions: + security-events: write + packages: read + actions: read + contents: read jobs: analyze: - name: Analyze + name: Analyze (${{ matrix.language }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: - language: [ 'java' ] + include: + - language: actions + build-mode: none + - language: java-kotlin + build-mode: manual steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up JDK - uses: actions/setup-java@v3 - with: - distribution: 'temurin' - java-version: 21 - - - name: Cache Maven packages - uses: actions/cache@v3 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - - - name: Build with Maven - run: mvn -B package --file extra/pom.xml - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 - with: - category: "/language:${{ matrix.language }}" + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Set up JDK + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: 21 + + - name: Cache Maven packages + uses: actions/cache@v5 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + + - name: Build with Maven + if: matrix.build-mode == 'manual' + run: mvn -B package --file extra/pom.xml + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: '/language:${{ matrix.language }}' diff --git a/.github/workflows/cross-repo-issue.yml b/.github/workflows/cross-repo-issue.yml index a2aa9471ecf..5d2e512d4c6 100644 --- a/.github/workflows/cross-repo-issue.yml +++ b/.github/workflows/cross-repo-issue.yml @@ -2,9 +2,12 @@ name: Cross-repo Issue Creation on: pull_request_target: - types: [closed] + types: [ closed ] branches: - - "master" + - 'master' + +permissions: + contents: read jobs: cross-repo: @@ -12,7 +15,7 @@ jobs: steps: - name: Generate token id: generate_token - uses: tibdex/github-app-token@v1 + uses: tibdex/github-app-token@v2.1.0 with: app_id: ${{ secrets.XREPO_APP_ID }} private_key: ${{ secrets.XREPO_PEM }} diff --git a/.github/workflows/docker-image-publish.yml b/.github/workflows/docker-image-publish.yml index 39964eb69aa..7f993ade73d 100644 --- a/.github/workflows/docker-image-publish.yml +++ b/.github/workflows/docker-image-publish.yml @@ -5,6 +5,10 @@ on: tags: - '*' +permissions: + contents: read + packages: write + env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} @@ -13,13 +17,10 @@ jobs: build: name: Publish Docker image for new tag/release runs-on: ubuntu-latest - permissions: - contents: read - packages: write strategy: matrix: java: [ 21 ] - dockerfile-path: [Dockerfile, Dockerfile-modules] + dockerfile-path: [ Dockerfile, Dockerfile-modules ] include: - dockerfile-path: Dockerfile build-cmd: mvn clean package -Dcheckstyle.skip -Dmaven.test.skip=true @@ -30,10 +31,10 @@ jobs: package-name: ghcr.io/${{ github.repository }}-bundle steps: - name: Check out Repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: distribution: 'temurin' cache: 'maven' @@ -56,13 +57,13 @@ jobs: images: ${{ matrix.package-name }} - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Build and push Docker image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . file: ${{ matrix.dockerfile-path }} diff --git a/.github/workflows/issue_prioritization.yml b/.github/workflows/issue_prioritization.yml index fa56f9ee2ee..7b4df73b80b 100644 --- a/.github/workflows/issue_prioritization.yml +++ b/.github/workflows/issue_prioritization.yml @@ -1,9 +1,11 @@ name: Issue tracking + on: issues: types: - opened - pinned + jobs: track_issue: runs-on: ubuntu-latest diff --git a/.github/workflows/pr-functional-tests.yml b/.github/workflows/pr-functional-tests.yml index 0cf032e3e23..d512022413a 100644 --- a/.github/workflows/pr-functional-tests.yml +++ b/.github/workflows/pr-functional-tests.yml @@ -11,6 +11,11 @@ on: types: - created +permissions: + contents: read + actions: read + checks: write + jobs: build: runs-on: ubuntu-latest @@ -20,10 +25,10 @@ jobs: java: [ 21 ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: distribution: 'temurin' cache: 'maven' @@ -42,7 +47,7 @@ jobs: - name: Emitting run result of functional test if: always() - uses: dorny/test-reporter@v2.1.1 + uses: dorny/test-reporter@v2.5.0 with: name: 'Functional tests' working-directory: 'target/failsafe-reports' diff --git a/.github/workflows/pr-java-ci.yml b/.github/workflows/pr-java-ci.yml index 1a198dac001..d69d222592f 100644 --- a/.github/workflows/pr-java-ci.yml +++ b/.github/workflows/pr-java-ci.yml @@ -11,6 +11,11 @@ on: types: - created +permissions: + contents: read + actions: read + checks: write + jobs: build: runs-on: ubuntu-latest @@ -20,10 +25,10 @@ jobs: java: [ 21 ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' cache: 'maven' @@ -32,12 +37,3 @@ jobs: - name: Build with Maven run: mvn -B package --file extra/pom.xml - - - name: Publish JUnit Report - uses: mikepenz/action-junit-report@v5 - if: always() - with: - check_name: 'JUnit Test Report' - report_paths: '**/target/surefire-reports/TEST-*.xml' - fail_on_failure: true - annotate_only: true diff --git a/.github/workflows/pr-module-functional-tests.yml b/.github/workflows/pr-module-functional-tests.yml index e61814093b0..c3b04858677 100644 --- a/.github/workflows/pr-module-functional-tests.yml +++ b/.github/workflows/pr-module-functional-tests.yml @@ -11,6 +11,11 @@ on: types: - created +permissions: + contents: read + actions: read + checks: write + jobs: build: runs-on: ubuntu-latest @@ -20,10 +25,10 @@ jobs: java: [ 21 ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: distribution: 'temurin' cache: 'maven' @@ -46,7 +51,7 @@ jobs: - name: Emitting run result of functional test if: always() - uses: dorny/test-reporter@v2.1.1 + uses: dorny/test-reporter@v2.5.0 with: name: 'Module functional tests' working-directory: 'target/failsafe-reports' diff --git a/.github/workflows/release-asset-publish.yml b/.github/workflows/release-asset-publish.yml index fb1057d8ee8..bfa938bebe9 100644 --- a/.github/workflows/release-asset-publish.yml +++ b/.github/workflows/release-asset-publish.yml @@ -2,7 +2,7 @@ name: Publish release .jar on: workflow_run: - workflows: [Publish release] + workflows: [ Publish release ] types: - completed @@ -14,9 +14,9 @@ jobs: matrix: java: [ 21 ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: distribution: 'temurin' cache: 'maven' diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index c1ee08ab668..75ea23441de 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -5,17 +5,22 @@ on: tags: - '*' +permissions: + contents: read + jobs: update_release_draft: name: Publish release with notes + permissions: + contents: write runs-on: ubuntu-latest steps: - name: Create and publish release - uses: release-drafter/release-drafter@v5 + uses: release-drafter/release-drafter@v6 with: config-name: release-drafter-config.yml publish: true - name: "v${{ github.ref_name }}" + name: 'v${{ github.ref_name }}' tag: ${{ github.ref_name }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/slack-stale-pr.yml b/.github/workflows/slack-stale-pr.yml deleted file mode 100644 index a610c3e7de9..00000000000 --- a/.github/workflows/slack-stale-pr.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Post Stale PRs To Slack - -on: - # run Monday 9am and on-demand - workflow_dispatch: - schedule: - - cron: '0 9 * * 1' - -jobs: - fetch-PRs: - runs-on: ubuntu-latest - steps: - - name: Fetch pull requests - id: local - uses: paritytech/stale-pr-finder@v0.3.0 - with: - GITHUB_TOKEN: ${{ github.token }} - days-stale: 14 - ignoredLabels: "blocked" - - name: Post to a Slack channel - id: slack - uses: slackapi/slack-github-action@v1.27.1 - with: - channel-id: ${{ secrets.SLACK_CHANNEL_ID }} - slack-message: "${{ steps.local.outputs.message }}" - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} diff --git a/.github/workflows/trivy-security-check.yml b/.github/workflows/trivy-security-check.yml index 044b7e39af6..b73eda3b40d 100644 --- a/.github/workflows/trivy-security-check.yml +++ b/.github/workflows/trivy-security-check.yml @@ -1,27 +1,35 @@ -name: Security Check +name: Trivy Security Scan on: pull_request: - branches: [master] + branches: [ 'master' ] + schedule: + - cron: '0 3 * * 1' + +permissions: + contents: read jobs: build: name: Trivy security check + permissions: + security-events: write runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@0.33.1 with: scan-type: 'fs' + scan-ref: '.' ignore-unfixed: true format: 'sarif' output: 'trivy-results.sarif' severity: 'CRITICAL,HIGH' - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v4 with: sarif_file: 'trivy-results.sarif' From 18cb3e84090034de8c9a5a33428f13b96b15f315 Mon Sep 17 00:00:00 2001 From: Oleksandr Zhevedenko <720803+Net-burst@users.noreply.github.com> Date: Tue, 20 Jan 2026 08:35:45 -0500 Subject: [PATCH 13/38] Core: EID Permissions extension (#4349) --- .../server/auction/EidPermissionResolver.java | 81 ++++++ .../server/auction/ExchangeService.java | 51 ++-- .../ExtRequestPrebidDataEidPermissions.java | 27 +- .../server/validation/RequestValidator.java | 20 +- .../model/request/auction/Eid.groovy | 10 + .../request/auction/EidPermission.groovy | 30 +- .../server/functional/tests/EidsSpec.groovy | 257 +++++++++++++++++- .../auction/EidPermissionResolverTest.java | 251 +++++++++++++++++ .../server/auction/ExchangeServiceTest.java | 105 ++----- .../AuctionRequestFactoryTest.java | 12 +- .../validation/RequestValidatorTest.java | 41 ++- 11 files changed, 740 insertions(+), 145 deletions(-) create mode 100644 src/main/java/org/prebid/server/auction/EidPermissionResolver.java create mode 100644 src/test/java/org/prebid/server/auction/EidPermissionResolverTest.java diff --git a/src/main/java/org/prebid/server/auction/EidPermissionResolver.java b/src/main/java/org/prebid/server/auction/EidPermissionResolver.java new file mode 100644 index 00000000000..6a19fd7c82b --- /dev/null +++ b/src/main/java/org/prebid/server/auction/EidPermissionResolver.java @@ -0,0 +1,81 @@ +package org.prebid.server.auction; + +import com.iab.openrtb.request.Eid; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidDataEidPermissions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class EidPermissionResolver { + + private static final String WILDCARD_BIDDER = "*"; + + private static final ExtRequestPrebidDataEidPermissions DEFAULT_RULE = ExtRequestPrebidDataEidPermissions.builder() + .bidders(Collections.singletonList(WILDCARD_BIDDER)) + .build(); + + private static final EidPermissionResolver EMPTY = new EidPermissionResolver(Collections.emptyList()); + + private final List eidPermissions; + + private EidPermissionResolver(List eidPermissions) { + this.eidPermissions = new ArrayList<>(eidPermissions); + this.eidPermissions.add(DEFAULT_RULE); + } + + public static EidPermissionResolver of(List eidPermissions) { + return new EidPermissionResolver(eidPermissions); + } + + public static EidPermissionResolver empty() { + return EMPTY; + } + + public List resolveAllowedEids(List userEids, String bidder) { + return CollectionUtils.emptyIfNull(userEids) + .stream() + .filter(userEid -> isAllowed(userEid, bidder)) + .toList(); + } + + private boolean isAllowed(Eid eid, String bidder) { + final Map> matchingRulesBySpecificity = eidPermissions + .stream() + .filter(rule -> isRuleMatched(eid, rule)) + .collect(Collectors.groupingBy(this::getRuleSpecificity)); + + final int highestSpecificityMatchingRules = Collections.max(matchingRulesBySpecificity.keySet()); + return matchingRulesBySpecificity.get(highestSpecificityMatchingRules).stream() + .anyMatch(eidPermission -> isBidderAllowed(bidder, eidPermission.getBidders())); + } + + private int getRuleSpecificity(ExtRequestPrebidDataEidPermissions eidPermission) { + return (int) Stream.of( + eidPermission.getInserter(), + eidPermission.getSource(), + eidPermission.getMatcher(), + eidPermission.getMm()) + .filter(Objects::nonNull) + .count(); + } + + private boolean isRuleMatched(Eid eid, ExtRequestPrebidDataEidPermissions eidPermission) { + return (eidPermission.getInserter() == null || eidPermission.getInserter().equals(eid.getInserter())) + && (eidPermission.getSource() == null || eidPermission.getSource().equals(eid.getSource())) + && (eidPermission.getMatcher() == null || eidPermission.getMatcher().equals(eid.getMatcher())) + && (eidPermission.getMm() == null || eidPermission.getMm().equals(eid.getMm())); + } + + private boolean isBidderAllowed(String bidder, List ruleBidders) { + return ruleBidders == null || ruleBidders.stream() + .anyMatch(allowedBidder -> StringUtils.equalsIgnoreCase(allowedBidder, bidder) + || WILDCARD_BIDDER.equals(allowedBidder)); + } +} diff --git a/src/main/java/org/prebid/server/auction/ExchangeService.java b/src/main/java/org/prebid/server/auction/ExchangeService.java index c4ae087d205..428510be069 100644 --- a/src/main/java/org/prebid/server/auction/ExchangeService.java +++ b/src/main/java/org/prebid/server/auction/ExchangeService.java @@ -86,7 +86,6 @@ import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidBidderConfig; import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidCache; import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidData; -import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidDataEidPermissions; import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidMultiBid; import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidSchain; import org.prebid.server.proto.openrtb.ext.request.ExtRequestTargeting; @@ -129,7 +128,6 @@ public class ExchangeService { private static final String ALL_BIDDERS_CONFIG = "*"; private static final Integer DEFAULT_MULTIBID_LIMIT_MIN = 1; private static final Integer DEFAULT_MULTIBID_LIMIT_MAX = 9; - private static final String EID_ALLOWED_FOR_ALL_BIDDERS = "*"; private static final BigDecimal THOUSAND = BigDecimal.valueOf(1000); private static final Set BIDDER_FIELDS_EXCEPTION_LIST = Set.of( "adunitcode", "storedrequest", "options", "is_rewarded_inventory"); @@ -538,9 +536,9 @@ private Future> makeAuctionParticipation( final ExtRequest requestExt = bidRequest.getExt(); final ExtRequestPrebid prebid = requestExt != null ? requestExt.getPrebid() : null; final Map biddersToConfigs = getBiddersToConfigs(prebid); - final Map> eidPermissions = getEidPermissions(prebid); + final EidPermissionResolver eidPermissionResolver = getEidPermissions(prebid); final Map> bidderToUserAndDevice = - prepareUsersAndDevices(bidders, context, aliases, biddersToConfigs, eidPermissions); + prepareUsersAndDevices(bidders, context, aliases, biddersToConfigs, eidPermissionResolver); return privacyEnforcementService.mask(context, bidderToUserAndDevice, aliases) .map(bidderToPrivacyResult -> getAuctionParticipation( @@ -578,14 +576,12 @@ private Map getBiddersToConfigs(ExtRequestPrebid pr return bidderToConfig; } - private Map> getEidPermissions(ExtRequestPrebid prebid) { - final ExtRequestPrebidData prebidData = prebid != null ? prebid.getData() : null; - final List eidPermissions = prebidData != null - ? prebidData.getEidPermissions() - : null; - return CollectionUtils.emptyIfNull(eidPermissions).stream() - .collect(Collectors.toMap(ExtRequestPrebidDataEidPermissions::getSource, - ExtRequestPrebidDataEidPermissions::getBidders)); + private EidPermissionResolver getEidPermissions(ExtRequestPrebid prebid) { + return Optional.ofNullable(prebid) + .map(ExtRequestPrebid::getData) + .map(ExtRequestPrebidData::getEidPermissions) + .map(EidPermissionResolver::of) + .orElse(EidPermissionResolver.empty()); } private static List firstPartyDataBidders(ExtRequest requestExt) { @@ -594,11 +590,12 @@ private static List firstPartyDataBidders(ExtRequest requestExt) { return data == null ? null : data.getBidders(); } - private Map> prepareUsersAndDevices(List bidders, - AuctionContext context, - BidderAliases aliases, - Map biddersToConfigs, - Map> eidPermissions) { + private Map> prepareUsersAndDevices( + List bidders, + AuctionContext context, + BidderAliases aliases, + Map biddersToConfigs, + EidPermissionResolver eidPermissionResolver) { final BidRequest bidRequest = context.getBidRequest(); final List firstPartyDataBidders = firstPartyDataBidders(bidRequest.getExt()); @@ -610,7 +607,7 @@ private Map> prepareUsersAndDevices(List bidd final boolean useFirstPartyData = firstPartyDataBidders == null || firstPartyDataBidders.stream() .anyMatch(fpdBidder -> StringUtils.equalsIgnoreCase(fpdBidder, bidder)); final User preparedUser = prepareUser( - bidder, context, aliases, useFirstPartyData, fpdConfig, eidPermissions); + bidder, context, aliases, useFirstPartyData, fpdConfig, eidPermissionResolver); final Device preparedDevice = prepareDevice( bidRequest.getDevice(), fpdConfig, useFirstPartyData); bidderToUserAndDevice.put(bidder, Pair.of(preparedUser, preparedDevice)); @@ -623,13 +620,13 @@ private User prepareUser(String bidder, BidderAliases aliases, boolean useFirstPartyData, ExtBidderConfigOrtb fpdConfig, - Map> eidPermissions) { + EidPermissionResolver eidPermissionResolver) { final User user = context.getBidRequest().getUser(); final ExtUser extUser = user != null ? user.getExt() : null; final UpdateResult buyerUidUpdateResult = uidUpdater.updateUid(bidder, context, aliases); final List userEids = extractUserEids(user); - final List allowedUserEids = resolveAllowedEids(userEids, bidder, eidPermissions); + final List allowedUserEids = eidPermissionResolver.resolveAllowedEids(userEids, bidder); final boolean shouldUpdateUserEids = allowedUserEids.size() != CollectionUtils.emptyIfNull(userEids).size(); final boolean shouldCleanExtPrebid = extUser != null && extUser.getPrebid() != null; final boolean shouldCleanExtData = extUser != null && extUser.getData() != null && !useFirstPartyData; @@ -669,20 +666,6 @@ private List extractUserEids(User user) { return user != null ? user.getEids() : null; } - private List resolveAllowedEids(List userEids, String bidder, Map> eidPermissions) { - return CollectionUtils.emptyIfNull(userEids) - .stream() - .filter(userEid -> isUserEidAllowed(userEid.getSource(), eidPermissions, bidder)) - .toList(); - } - - private boolean isUserEidAllowed(String source, Map> eidPermissions, String bidder) { - final List allowedBidders = eidPermissions.get(source); - return CollectionUtils.isEmpty(allowedBidders) || allowedBidders.stream() - .anyMatch(allowedBidder -> StringUtils.equalsIgnoreCase(allowedBidder, bidder) - || EID_ALLOWED_FOR_ALL_BIDDERS.equals(allowedBidder)); - } - private List getAuctionParticipation( List bidderPrivacyResults, BidRequest bidRequest, diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtRequestPrebidDataEidPermissions.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtRequestPrebidDataEidPermissions.java index 82b2e6007a1..2373c40e886 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtRequestPrebidDataEidPermissions.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtRequestPrebidDataEidPermissions.java @@ -1,24 +1,43 @@ package org.prebid.server.proto.openrtb.ext.request; import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Builder; import lombok.Value; import java.util.List; -/** - * Defines the contract for bidrequest.ext.prebid.data.eidPermissions - */ -@Value(staticConstructor = "of") +@Value +@Builder public class ExtRequestPrebidDataEidPermissions { + /** + * Defines the contract for bidrequest.ext.prebid.data.eidPermissions.inserter + */ + String inserter; + /** * Defines the contract for bidrequest.ext.prebid.data.eidPermissions.source */ String source; + /** + * Defines the contract for bidrequest.ext.prebid.data.eidPermissions.matcher + */ + String matcher; + + /** + * Defines the contract for bidrequest.ext.prebid.data.eidPermissions.mm + */ + Integer mm; + /** * Defines the contract for bidrequest.ext.prebid.data.eidPermissions.bidders */ @JsonFormat(without = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) List bidders; + + @Deprecated + public static ExtRequestPrebidDataEidPermissions of(String source, List bidders) { + return new ExtRequestPrebidDataEidPermissions(null, source, null, null, bidders); + } } diff --git a/src/main/java/org/prebid/server/validation/RequestValidator.java b/src/main/java/org/prebid/server/validation/RequestValidator.java index d4b9fb06726..958c59d3a5a 100644 --- a/src/main/java/org/prebid/server/validation/RequestValidator.java +++ b/src/main/java/org/prebid/server/validation/RequestValidator.java @@ -376,7 +376,7 @@ private void validateEidPermissions(List eid boolean isDebugEnabled, List warnings) throws ValidationException { - if (eidPermissions == null) { + if (CollectionUtils.isEmpty(eidPermissions)) { return; } @@ -385,14 +385,24 @@ private void validateEidPermissions(List eid throw new ValidationException("request.ext.prebid.data.eidpermissions[i] can't be null"); } - validateEidPermissionSource(eidPermission.getSource()); + validateEidPermissionCriteria( + eidPermission.getInserter(), + eidPermission.getSource(), + eidPermission.getMatcher(), + eidPermission.getMm()); + validateEidPermissionBidders(eidPermission.getBidders(), aliases, isDebugEnabled, warnings); } } - private void validateEidPermissionSource(String source) throws ValidationException { - if (StringUtils.isEmpty(source)) { - throw new ValidationException("Missing required value request.ext.prebid.data.eidPermissions[].source"); + private void validateEidPermissionCriteria(String inserter, + String source, + String matcher, + Integer mm) throws ValidationException { + + if (StringUtils.isAllEmpty(inserter, source, matcher) && mm == null) { + throw new ValidationException("Missing required parameter(s) in request.ext.prebid.data.eidPermissions[]. " + + "Either one or a combination of inserter, source, matcher, or mm should be defined."); } } diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Eid.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Eid.groovy index 2cb344f6967..25e14e62126 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Eid.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Eid.groovy @@ -25,4 +25,14 @@ class Eid { it.matchMethod = PBSUtils.randomNumber } } + + static Eid from(EidPermission eidPermission, List uids = [Uid.defaultUid]) { + new Eid().tap { + it.source = eidPermission.source + it.uids = uids + it.inserter = eidPermission.inserter + it.matcher = eidPermission.matcher + it.matchMethod = eidPermission.matchMethod + } + } } diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/EidPermission.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/EidPermission.groovy index df77f8f1fbe..5807c6f9241 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/EidPermission.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/EidPermission.groovy @@ -1,11 +1,39 @@ package org.prebid.server.functional.model.request.auction +import com.fasterxml.jackson.annotation.JsonProperty import groovy.transform.ToString import org.prebid.server.functional.model.bidder.BidderName +import org.prebid.server.functional.util.PBSUtils + +import static org.prebid.server.functional.model.bidder.BidderName.GENERIC @ToString(includeNames = true, ignoreNulls = true) class EidPermission { String source - List bidders + String inserter + String matcher + @JsonProperty("mm") + Integer matchMethod + List bidders = [GENERIC] + + static EidPermission getDefaultEidPermission(List bidders = [GENERIC]) { + new EidPermission().tap { + it.source = PBSUtils.randomString + it.inserter = PBSUtils.randomString + it.matcher = PBSUtils.randomString + it.matchMethod = PBSUtils.randomNumber + it.bidders = bidders + } + } + + static EidPermission from(Eid eid, List bidders = [GENERIC]) { + new EidPermission().tap { + it.source = eid.source + it.inserter = eid.inserter + it.matcher = eid.matcher + it.matchMethod = eid.matchMethod + it.bidders = bidders + } + } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/EidsSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/EidsSpec.groovy index 46b0ff2a5aa..6bc172f3fa4 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/EidsSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/EidsSpec.groovy @@ -13,9 +13,12 @@ import org.prebid.server.functional.model.request.auction.UserExt import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.util.PBSUtils +import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST import static org.prebid.server.functional.model.bidder.BidderName.ALIAS import static org.prebid.server.functional.model.bidder.BidderName.GENERIC +import static org.prebid.server.functional.model.bidder.BidderName.GENERIC_CAMEL_CASE import static org.prebid.server.functional.model.bidder.BidderName.OPENX +import static org.prebid.server.functional.model.bidder.BidderName.RUBICON import static org.prebid.server.functional.model.bidder.BidderName.UNKNOWN import static org.prebid.server.functional.model.bidder.BidderName.WILDCARD import static org.prebid.server.functional.model.request.auction.DebugCondition.DISABLED @@ -224,7 +227,7 @@ class EidsSpec extends BaseSpec { imp[0].ext.prebid.bidder.openx = Openx.defaultOpenx ext.prebid.data = new ExtRequestPrebidData( eidpermissions: [new EidPermission(source: PBSUtils.randomString, bidders: eidsBidder), - new EidPermission(source: PBSUtils.randomString)]) + new EidPermission(source: PBSUtils.randomString, bidders: null)]) } when: "PBS processes auction request" @@ -338,4 +341,256 @@ class EidsSpec extends BaseSpec { and: "Bid response shouldn't contain warning" assert !bidResponse.ext.warnings } + + def "PBS should pass user.eids to all bidders when any of required eid permissions doesn't match"() { + given: "Default BidRequest with eids" + def eidPermission = EidPermission.getDefaultEidPermission([RUBICON]) + def userEid = updateEidClosure(Eid.from(eidPermission)) + + def bidRequest = BidRequest.defaultBidRequest.tap { + user = new User(eids: [userEid]) + ext.prebid.data = new ExtRequestPrebidData(eidpermissions: [eidPermission]) + } + + when: "PBS processes auction request" + def bidResponse = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain requested eids" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.user.eids == [userEid] + + and: "Bid response shouldn't contain any errors and warnings" + assert !bidResponse.ext.errors + assert !bidResponse.ext.warnings + + where: + updateEidClosure << [ + { Eid eid -> eid.tap { it.inserter = null } }, + { Eid eid -> eid.tap { it.matcher = null } }, + { Eid eid -> eid.tap { it.matchMethod = null } }, + + { Eid eid -> eid.tap { it.inserter = "" } }, + { Eid eid -> eid.tap { it.matcher = "" } }, + + { Eid eid -> eid.tap { it.source = PBSUtils.randomString } }, + { Eid eid -> eid.tap { it.inserter = PBSUtils.randomString } }, + { Eid eid -> eid.tap { it.matcher = PBSUtils.randomString } }, + { Eid eid -> eid.tap { it.matchMethod = PBSUtils.randomNumber } } + ] + } + + def "PBS shouldn't pass user.eids to unmatched bidders when eidPermissions fields match user.eids"() { + given: "Default BidRequest with eids" + def eidPermissionWithUnmatchedBidder = eidPermission.tap { + bidders = [RUBICON] + } + def userEid = updateEidClosure(eidPermissionWithUnmatchedBidder) + + def bidRequest = BidRequest.defaultBidRequest.tap { + user = new User(eids: [userEid]) + ext.prebid.data = new ExtRequestPrebidData(eidpermissions: [eidPermissionWithUnmatchedBidder]) + } + + when: "PBS processes auction request" + def bidResponse = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain eids" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.user.eids + + and: "Bid response shouldn't contain any errors and warnings" + assert !bidResponse.ext.errors + assert !bidResponse.ext.warnings + + where: + eidPermission | updateEidClosure + new EidPermission(source: PBSUtils.randomString) | { EidPermission eid -> Eid.from(eid) } + new EidPermission(source: PBSUtils.randomString) | { EidPermission eid -> Eid.getDefaultEid().tap { it.source = eid.source } } + new EidPermission(inserter: PBSUtils.randomString) | { EidPermission eid -> Eid.from(eid).tap { it.source = PBSUtils.randomString } } + new EidPermission(inserter: PBSUtils.randomString) | { EidPermission eid -> Eid.getDefaultEid().tap { it.inserter = eid.inserter } } + new EidPermission(matcher: PBSUtils.randomString) | { EidPermission eid -> Eid.from(eid).tap { it.source = PBSUtils.randomString } } + new EidPermission(matcher: PBSUtils.randomString) | { EidPermission eid -> Eid.getDefaultEid().tap { it.matcher = eid.matcher } } + new EidPermission(matchMethod: PBSUtils.randomNumber) | { EidPermission eid -> Eid.from(eid).tap { it.source = PBSUtils.randomString } } + new EidPermission(matchMethod: PBSUtils.randomNumber) | { EidPermission eid -> Eid.getDefaultEid().tap { it.matchMethod = eid.matchMethod } } + } + + def "PBS should filter only unauthorized eids when multiple eids with different permissions are present"() { + given: "Default BidRequest with eids" + def eidPermissionWithUnmatchedBidder = EidPermission.getDefaultEidPermission([RUBICON]) + def userEid = Eid.from(eidPermissionWithUnmatchedBidder) + def properEids = [Eid.defaultEid, Eid.defaultEid] + def bidRequest = BidRequest.defaultBidRequest.tap { + user = new User(eids: properEids + userEid) + ext.prebid.data = new ExtRequestPrebidData(eidpermissions: [eidPermissionWithUnmatchedBidder]) + } + + when: "PBS processes auction request" + def bidResponse = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain requested eids" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.user.eids == properEids + + and: "Bid response shouldn't contain any errors and warnings" + assert !bidResponse.ext.errors + assert !bidResponse.ext.warnings + } + + def "PBS should pass user.eids to matched bidders when eidPermissions fields match user.eids"() { + given: "Default BidRequest with eids" + def eidPermissionAllowingCurrentBidder = eidPermission.tap { + bidders = [[GENERIC, GENERIC_CAMEL_CASE].shuffled().first()] + } + def userEid = updateEidClosure(eidPermissionAllowingCurrentBidder) + + def bidRequest = BidRequest.defaultBidRequest.tap { + user = new User(eids: [userEid]) + ext.prebid.data = new ExtRequestPrebidData(eidpermissions: [eidPermissionAllowingCurrentBidder]) + } + + when: "PBS processes auction request" + def bidResponse = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain requested eids" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.user.eids == [userEid] + + and: "Bid response shouldn't contain any errors and warnings" + assert !bidResponse.ext.errors + assert !bidResponse.ext.warnings + + where: + eidPermission | updateEidClosure + new EidPermission(source: PBSUtils.randomString) | { EidPermission eid -> Eid.from(eid) } + new EidPermission(source: PBSUtils.randomString) | { EidPermission eid -> Eid.getDefaultEid().tap { it.source = eid.source } } + new EidPermission(inserter: PBSUtils.randomString) | { EidPermission eid -> Eid.from(eid).tap { it.source = PBSUtils.randomString } } + new EidPermission(inserter: PBSUtils.randomString) | { EidPermission eid -> Eid.getDefaultEid().tap { it.inserter = eid.inserter } } + new EidPermission(matcher: PBSUtils.randomString) | { EidPermission eid -> Eid.from(eid).tap { it.source = PBSUtils.randomString } } + new EidPermission(matcher: PBSUtils.randomString) | { EidPermission eid -> Eid.getDefaultEid().tap { it.matcher = eid.matcher } } + new EidPermission(matchMethod: PBSUtils.randomNumber) | { EidPermission eid -> Eid.from(eid).tap { it.source = PBSUtils.randomString } } + new EidPermission(matchMethod: PBSUtils.randomNumber) | { EidPermission eid -> Eid.getDefaultEid().tap { it.matchMethod = eid.matchMethod } } + } + + def "PBS should apply most specific eidPermissions rule when multiple rules match"() { + given: "Default BidRequest with eids" + def userEid = Eid.getDefaultEid() + def moreSpecificEidPermission = moreSpecificPermissionClosure(userEid).tap { + it.bidders = [RUBICON] + } + def lessSpecificEidPermission = lessSpecificPermissionClosure(userEid).tap { + it.bidders = [GENERIC] + } + def bidRequest = BidRequest.defaultBidRequest.tap { + user = new User(eids: [userEid]) + ext.prebid.data = new ExtRequestPrebidData(eidpermissions: [moreSpecificEidPermission, lessSpecificEidPermission].shuffled()) + } + + when: "PBS processes auction request" + def bidResponse = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request shouldn't contain eids" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert !bidderRequest.user.eids + + and: "Bid response shouldn't contain any errors and warnings" + assert !bidResponse.ext.errors + assert !bidResponse.ext.warnings + + where: + moreSpecificPermissionClosure | lessSpecificPermissionClosure + ({ Eid eid -> EidPermission.from(eid) }) | ({ Eid eid -> EidPermission.from(eid).tap { it.source = null } }) + ({ Eid eid -> EidPermission.from(eid) }) | ({ Eid eid -> EidPermission.from(eid).tap { it.inserter = null } }) + ({ Eid eid -> EidPermission.from(eid) }) | ({ Eid eid -> EidPermission.from(eid).tap { it.matcher = null } }) + ({ Eid eid -> EidPermission.from(eid) }) | ({ Eid eid -> EidPermission.from(eid).tap { it.matchMethod = null } }) + ({ Eid eid -> EidPermission.from(eid).tap { it.source = null } }) | ({ Eid eid -> new EidPermission(inserter: eid.inserter, matcher: eid.matcher) }) + ({ Eid eid -> new EidPermission(inserter: eid.inserter, matcher: eid.matcher) }) | ({ Eid eid -> new EidPermission(matchMethod: eid.matchMethod) }) + } + + def "PBS should allow access to bidder defined in most specific rule when multiple rules match"() { + given: "Default BidRequest with eids" + def userEid = Eid.getDefaultEid() + def moreSpecificEidPermission = moreSpecificPermissionClosure(userEid).tap { + it.bidders = [GENERIC] + } + def lessSpecificEidPermission = lessSpecificPermissionClosure(userEid).tap { + it.bidders = [RUBICON] + } + def bidRequest = BidRequest.defaultBidRequest.tap { + user = new User(eids: [userEid]) + ext.prebid.data = new ExtRequestPrebidData(eidpermissions: [moreSpecificEidPermission, lessSpecificEidPermission].shuffled()) + } + + when: "PBS processes auction request" + def bidResponse = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain requested eids" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.user.eids == [userEid] + + and: "Bid response shouldn't contain any errors and warnings" + assert !bidResponse.ext.errors + assert !bidResponse.ext.warnings + + where: + moreSpecificPermissionClosure | lessSpecificPermissionClosure + ({ Eid eid -> EidPermission.from(eid) }) | ({ Eid eid -> EidPermission.from(eid).tap { it.source = null } }) + ({ Eid eid -> EidPermission.from(eid) }) | ({ Eid eid -> EidPermission.from(eid).tap { it.inserter = null } }) + ({ Eid eid -> EidPermission.from(eid) }) | ({ Eid eid -> EidPermission.from(eid).tap { it.matcher = null } }) + ({ Eid eid -> EidPermission.from(eid) }) | ({ Eid eid -> EidPermission.from(eid).tap { it.matchMethod = null } }) + ({ Eid eid -> EidPermission.from(eid).tap { it.source = null } }) | ({ Eid eid -> new EidPermission(inserter: eid.inserter, matcher: eid.matcher) }) + ({ Eid eid -> new EidPermission(inserter: eid.inserter, matcher: eid.matcher) }) | ({ Eid eid -> new EidPermission(matchMethod: eid.matchMethod) }) + } + + def "PBS should apply permissions from any matching rule when specificity is equal"() { + given: "Default BidRequest with eids" + def userEid = Eid.getDefaultEid() + def allowingRule = allowingPermissionClosure(userEid).tap { + it.bidders = [GENERIC] + } + def restrictingRule = restrictingPermissionClosure(userEid).tap { + it.bidders = [RUBICON] + } + def bidRequest = BidRequest.defaultBidRequest.tap { + user = new User(eids: [userEid]) + ext.prebid.data = new ExtRequestPrebidData(eidpermissions: [allowingRule, restrictingRule].shuffled()) + } + + when: "PBS processes auction request" + def bidResponse = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder request should contain requested eids" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.user.eids == [userEid] + + and: "Bid response shouldn't contain any errors and warnings" + assert !bidResponse.ext.errors + assert !bidResponse.ext.warnings + + where: + allowingPermissionClosure | restrictingPermissionClosure + ({ Eid eid -> new EidPermission(source: eid.source) }) | ({ Eid eid -> new EidPermission(inserter: eid.inserter) }) + ({ Eid eid -> new EidPermission(matcher: eid.matcher) }) | ({ Eid eid -> new EidPermission(source: eid.source) }) + ({ Eid eid -> new EidPermission(matchMethod: eid.matchMethod) }) | ({ Eid eid -> new EidPermission(matcher: eid.matcher) }) + ({ Eid eid -> new EidPermission(source: eid.source, matcher: eid.matcher) }) | ({ Eid eid -> new EidPermission(inserter: eid.inserter, matchMethod: eid.matchMethod) }) + ({ Eid eid -> new EidPermission(source: eid.source, inserter: eid.inserter) }) | ({ Eid eid -> new EidPermission(matcher: eid.matcher, matchMethod: eid.matchMethod) }) + ({ Eid eid -> new EidPermission(source: eid.source, matchMethod: eid.matchMethod) }) | ({ Eid eid -> new EidPermission(inserter: eid.inserter, matcher: eid.matcher) }) + ({ Eid eid -> EidPermission.from(eid).tap { matchMethod = null } }) | ({ Eid eid -> EidPermission.from(eid).tap { matcher = null } }) + } + + def "PBS should throw an error when all eidPermissions fields are empty"() { + given: "Default bid request with invalid eidPermission" + def bidRequest = BidRequest.defaultBidRequest.tap { + ext.prebid.data = new ExtRequestPrebidData(eidpermissions: [new EidPermission()]) + } + + when: "PBS processes auction request" + defaultPbsService.sendAuctionRequest(bidRequest) + + then: "PBS should throw error" + def exception = thrown(PrebidServerException) + assert exception.statusCode == BAD_REQUEST.code() + assert exception.responseBody == "Invalid request format: " + + "Missing required parameter(s) in request.ext.prebid.data.eidPermissions[]. " + + "Either one or a combination of inserter, source, matcher, or mm should be defined." + } } diff --git a/src/test/java/org/prebid/server/auction/EidPermissionResolverTest.java b/src/test/java/org/prebid/server/auction/EidPermissionResolverTest.java new file mode 100644 index 00000000000..08d8627f172 --- /dev/null +++ b/src/test/java/org/prebid/server/auction/EidPermissionResolverTest.java @@ -0,0 +1,251 @@ +package org.prebid.server.auction; + +import com.iab.openrtb.request.Eid; +import org.junit.jupiter.api.Test; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidDataEidPermissions; + +import java.util.List; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + +public class EidPermissionResolverTest { + + @Test + public void resolveShouldFilterEidsWhenBidderIsNotAllowedForSourceIgnoringCase() { + // given + final List userEids = asList( + Eid.builder().source("source1").build(), + Eid.builder().source("source2").build()); + + final EidPermissionResolver resolver = EidPermissionResolver.of( + singletonList(ExtRequestPrebidDataEidPermissions.builder() + .source("source1") + .bidders(singletonList("OtHeRbIdDeR")) + .build())); + + // when and then + assertThat(resolver.resolveAllowedEids(userEids, "someBidder")) + .containsExactly(Eid.builder().source("source2").build()); + } + + @Test + void resolveShouldFilterEidsWhenBidderIsNotAllowedForInserterIgnoringCase() { + // given + final List userEids = asList( + Eid.builder().inserter("inserter1").build(), + Eid.builder().inserter("inserter2").build()); + + final EidPermissionResolver resolver = EidPermissionResolver.of( + singletonList(ExtRequestPrebidDataEidPermissions.builder() + .inserter("inserter1") + .bidders(singletonList("OtHeRbIdDeR")) + .build())); + + // when and then + assertThat(resolver.resolveAllowedEids(userEids, "someBidder")) + .containsExactly(Eid.builder().inserter("inserter2").build()); + } + + @Test + public void resolveShouldFilterEidsWhenBidderIsNotAllowedForMatcherIgnoringCase() { + // given + final List userEids = asList( + Eid.builder().matcher("matcher1").build(), + Eid.builder().matcher("matcher2").build()); + + final EidPermissionResolver resolver = EidPermissionResolver.of( + singletonList(ExtRequestPrebidDataEidPermissions.builder() + .matcher("matcher1") + .bidders(singletonList("OtHeRbIdDeR")) + .build())); + + // when and then + assertThat(resolver.resolveAllowedEids(userEids, "someBidder")) + .containsExactly(Eid.builder().matcher("matcher2").build()); + } + + @Test + public void resolveShouldFilterEidsWhenBidderIsNotAllowedForMm() { + // given + final List userEids = asList(Eid.builder().mm(1).build(), Eid.builder().mm(2).build()); + + final EidPermissionResolver resolver = EidPermissionResolver.of( + singletonList(ExtRequestPrebidDataEidPermissions.builder() + .mm(1) + .bidders(singletonList("OtHeRbIdDeR")) + .build())); + + // when and then + assertThat(resolver.resolveAllowedEids(userEids, "someBidder")) + .containsExactly(Eid.builder().mm(2).build()); + } + + @Test + public void resolveShouldFilterEidsWhenBidderIsNotAllowedUsingMultipleCriteria() { + // given + final List userEids = asList( + Eid.builder().inserter("inserter1").source("source1").matcher("matcher1").mm(1).build(), + Eid.builder().inserter("inserter2").source("source2").matcher("matcher2").mm(2).build()); + + final EidPermissionResolver resolver = EidPermissionResolver.of( + singletonList(ExtRequestPrebidDataEidPermissions.builder() + .inserter("inserter1") + .source("source1") + .matcher("matcher1") + .mm(1) + .bidders(singletonList("OtHeRbIdDeR")) + .build())); + + // when and then + assertThat(resolver.resolveAllowedEids(userEids, "someBidder")).containsExactly( + Eid.builder().inserter("inserter2").source("source2").matcher("matcher2").mm(2).build()); + } + + @Test + public void resolveShouldFilterEidsWhenEveryCriteriaMatches() { + // given + final List userEids = asList( + Eid.builder().inserter("inserter1").source("source1").matcher("matcher1").mm(1).build(), + Eid.builder().inserter("inserter2").source("source2").matcher("matcher2").mm(2).build()); + + final EidPermissionResolver resolver = EidPermissionResolver.of( + singletonList(ExtRequestPrebidDataEidPermissions.builder() + .inserter("inserter1") + .source("source2") + .matcher("matcher3") + .mm(4) + .bidders(singletonList("OtHeRbIdDeR")) + .build())); + + // when and then + assertThat(resolver.resolveAllowedEids(userEids, "someBidder")).containsExactly( + Eid.builder().inserter("inserter1").source("source1").matcher("matcher1").mm(1).build(), + Eid.builder().inserter("inserter2").source("source2").matcher("matcher2").mm(2).build()); + } + + @Test + public void resolveShouldFilterEidsWhenBidderIsNotAllowedUsingTheMostSpecificRule() { + // given + final List userEids = asList( + Eid.builder().inserter("inserter1").source("source1").matcher("matcher1").mm(1).build(), + Eid.builder().inserter("inserter2").source("source2").matcher("matcher2").mm(2).build()); + + final EidPermissionResolver resolver = EidPermissionResolver.of( + asList(ExtRequestPrebidDataEidPermissions.builder() + .inserter("inserter1") + .bidders(singletonList("someBidder")) + .build(), + ExtRequestPrebidDataEidPermissions.builder() + .inserter("inserter1") + .source("source1") + .matcher("matcher1") + .mm(1) + .bidders(singletonList("OtHeRbIdDeR")) + .build())); + + // when and then + assertThat(resolver.resolveAllowedEids(userEids, "someBidder")).containsExactly( + Eid.builder().inserter("inserter2").source("source2").matcher("matcher2").mm(2).build()); + } + + @Test + public void resolveShouldNotFilterUserExtEidsWhenBidderIsAllowedUsingTheMostSpecificRule() { + // given + final List userEids = asList( + Eid.builder().inserter("inserter1").source("source1").matcher("matcher1").mm(1).build(), + Eid.builder().inserter("inserter2").source("source2").matcher("matcher2").mm(2).build()); + + final EidPermissionResolver resolver = EidPermissionResolver.of( + asList(ExtRequestPrebidDataEidPermissions.builder() + .inserter("inserter1") + .bidders(singletonList("OtHeRbIdDeR")) + .build(), + ExtRequestPrebidDataEidPermissions.builder() + .inserter("inserter1") + .source("source1") + .matcher("matcher1") + .mm(1) + .bidders(singletonList("someBidder")) + .build())); + + // when and then + assertThat(resolver.resolveAllowedEids(userEids, "someBidder")).containsExactly( + Eid.builder().inserter("inserter1").source("source1").matcher("matcher1").mm(1).build(), + Eid.builder().inserter("inserter2").source("source2").matcher("matcher2").mm(2).build()); + } + + @Test + public void resolveShouldNotFilterUserExtEidsWhenBidderIsAllowedUsingMultipleSameSpecificityRules() { + // given + final List userEids = asList( + Eid.builder().inserter("inserter1").source("source1").matcher("matcher1").mm(1).build(), + Eid.builder().inserter("inserter2").source("source2").matcher("matcher2").mm(2).build()); + + final EidPermissionResolver resolver = EidPermissionResolver.of( + asList(ExtRequestPrebidDataEidPermissions.builder() + .inserter("inserter1") + .source("source1") + .bidders(singletonList("OtHeRbIdDeR")) + .build(), + ExtRequestPrebidDataEidPermissions.builder() + .matcher("matcher1") + .mm(1) + .bidders(singletonList("someBidder")) + .build())); + + // when and then + assertThat(resolver.resolveAllowedEids(userEids, "someBidder")).containsExactly( + Eid.builder().inserter("inserter1").source("source1").matcher("matcher1").mm(1).build(), + Eid.builder().inserter("inserter2").source("source2").matcher("matcher2").mm(2).build()); + } + + @Test + public void resolveShouldNotFilterEidsWhenEidsPermissionDoesNotContainSourceIgnoringCase() { + // given + final List userEids = singletonList(Eid.builder().source("source1").build()); + + final EidPermissionResolver resolver = EidPermissionResolver.of( + singletonList(ExtRequestPrebidDataEidPermissions.builder() + .source("source2") + .bidders(singletonList("OtHeRbIdDeR")) + .build())); + + // when and then + assertThat(resolver.resolveAllowedEids(userEids, "someBidder")) + .containsExactly(Eid.builder().source("source1").build()); + } + + @Test + public void resolveShouldNotFilterEidsWhenSourceAllowedForAllBiddersIgnoringCase() { + // given + final List userEids = singletonList(Eid.builder().source("source1").build()); + + final EidPermissionResolver resolver = EidPermissionResolver.of( + singletonList(ExtRequestPrebidDataEidPermissions.builder() + .source("source1") + .bidders(singletonList("*")) + .build())); + + // when and then + assertThat(resolver.resolveAllowedEids(userEids, "someBidder")) + .containsExactly(Eid.builder().source("source1").build()); + } + + @Test + public void resolveShouldNotFilterEidsWhenSourceAllowedForBidderIgnoringCase() { + // given + final List userEids = singletonList(Eid.builder().source("source1").build()); + + final EidPermissionResolver resolver = EidPermissionResolver.of( + singletonList(ExtRequestPrebidDataEidPermissions.builder() + .source("source1") + .bidders(singletonList("SoMeBiDdEr")) + .build())); + + // when and then + assertThat(resolver.resolveAllowedEids(userEids, "someBidder")) + .containsExactly(Eid.builder().source("source1").build()); + } +} diff --git a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java index 8e5291b4c1c..84ec69ecddb 100644 --- a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java +++ b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java @@ -168,7 +168,6 @@ import java.time.ZoneId; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.List; @@ -1243,7 +1242,7 @@ public void shouldReturnSeparateSeatBidsForTheSameBidderIfBiddersAliasAndBidderW builder -> builder.ext(ExtRequest.of(ExtRequestPrebid.builder() .auctiontimestamp(1000L) .build())))) - .originalPriceFloors(Collections.emptyMap()) + .originalPriceFloors(emptyMap()) .build()), any(), any(), @@ -1266,7 +1265,7 @@ public void shouldReturnSeparateSeatBidsForTheSameBidderIfBiddersAliasAndBidderW builder -> builder.ext(ExtRequest.of(ExtRequestPrebid.builder() .auctiontimestamp(1000L) .build())))) - .originalPriceFloors(Collections.emptyMap()) + .originalPriceFloors(emptyMap()) .build()), any(), any(), @@ -1506,7 +1505,7 @@ public void shouldCallBidResponseCreatorWithExpectedParamsAndUpdateDebugErrors() final ExtRequestPrebidMultiBid multiBid5 = ExtRequestPrebidMultiBid.of("bidder6", Arrays.asList("bidder4", "bidder5"), 0, "bi6"); final ExtRequestPrebidMultiBid multiBid6 = ExtRequestPrebidMultiBid.of(null, - Collections.emptyList(), 0, "bi7"); + emptyList(), 0, "bi7"); final ExtRequestTargeting targeting = givenTargeting(true); final ObjectNode events = mapper.createObjectNode(); @@ -2136,7 +2135,7 @@ public void shouldAddMultibidInfoOnlyAboutRequestedBidder() { // given final BidRequest bidRequest = givenBidRequest(givenSingleImp(singletonMap("someBidder", 1)), builder -> builder.ext(ExtRequest.of(ExtRequestPrebid.builder() - .multibid(Collections.singletonList( + .multibid(singletonList( ExtRequestPrebidMultiBid.of(null, asList("someBidder", "anotherBidder"), 3, null))) .build()))); @@ -2230,50 +2229,6 @@ public void shouldPassUserDataAndExtDataOnlyForAllowedBidder() { tuple("keyword", "male", 133, Geo.EMPTY, eids, null, null)); } - @Test - public void shouldFilterUserExtEidsWhenBidderIsNotAllowedForSourceIgnoringCase() { - testUserEidsPermissionFiltering( - // given - asList(Eid.builder().source("source1").build(), Eid.builder().source("source2").build()), - singletonList(ExtRequestPrebidDataEidPermissions.of("source1", singletonList("OtHeRbIdDeR"))), - emptyMap(), - // expected - singletonList(Eid.builder().source("source2").build())); - } - - @Test - public void shouldNotFilterUserExtEidsWhenEidsPermissionDoesNotContainSourceIgnoringCase() { - testUserEidsPermissionFiltering( - // given - singletonList(Eid.builder().source("source1").build()), - singletonList(ExtRequestPrebidDataEidPermissions.of("source2", singletonList("OtHeRbIdDeR"))), - emptyMap(), - // expected - singletonList(Eid.builder().source("source1").build())); - } - - @Test - public void shouldNotFilterUserExtEidsWhenSourceAllowedForAllBiddersIgnoringCase() { - testUserEidsPermissionFiltering( - // given - singletonList(Eid.builder().source("source1").build()), - singletonList(ExtRequestPrebidDataEidPermissions.of("source1", singletonList("*"))), - emptyMap(), - // expected - singletonList(Eid.builder().source("source1").build())); - } - - @Test - public void shouldNotFilterUserExtEidsWhenSourceAllowedForBidderIgnoringCase() { - testUserEidsPermissionFiltering( - // given - singletonList(Eid.builder().source("source1").build()), - singletonList(ExtRequestPrebidDataEidPermissions.of("source1", singletonList("SoMeBiDdEr"))), - emptyMap(), - // expected - singletonList(Eid.builder().source("source1").build())); - } - @Test public void shouldFilterUserExtEidsWhenBidderIsNotAllowedForSourceAndSetNullIfNoEidsLeft() { // given @@ -2285,8 +2240,10 @@ public void shouldFilterUserExtEidsWhenBidderIsNotAllowedForSourceAndSetNullIfNo builder -> builder .ext(ExtRequest.of(ExtRequestPrebid.builder() .data(ExtRequestPrebidData.of(null, singletonList( - ExtRequestPrebidDataEidPermissions.of("source1", - singletonList("otherBidder"))))) + ExtRequestPrebidDataEidPermissions.builder() + .source("source1") + .bidders(singletonList("otherBidder")) + .build()))) .build())) .user(User.builder() .eids(singletonList(Eid.builder().source("source1").build())) @@ -2321,8 +2278,10 @@ public void shouldFilterUserExtEidsWhenBidderPermissionsGivenToBidderAliasOnly() .ext(ExtRequest.of(ExtRequestPrebid.builder() .aliases(singletonMap("someBidder", "someBidderAlias")) .data(ExtRequestPrebidData.of(null, singletonList( - ExtRequestPrebidDataEidPermissions.of("source1", - singletonList("someBidderAlias"))))) + ExtRequestPrebidDataEidPermissions.builder() + .source("source1") + .bidders(singletonList("someBidderAlias")) + .build()))) .build())) .user(User.builder() .eids(singletonList(Eid.builder().source("source1").build())) @@ -2357,8 +2316,10 @@ public void shouldFilterUserExtEidsWhenPermissionsGivenToBidderButNotForAlias() .ext(ExtRequest.of(ExtRequestPrebid.builder() .aliases(singletonMap("someBidder", "someBidderAlias")) .data(ExtRequestPrebidData.of(null, singletonList( - ExtRequestPrebidDataEidPermissions.of("source1", - singletonList("someBidder"))))) + ExtRequestPrebidDataEidPermissions.builder() + .source("source1") + .bidders(singletonList("someBidder")) + .build()))) .build())) .user(User.builder() .eids(singletonList(Eid.builder().source("source1").build())) @@ -4419,40 +4380,6 @@ private static BidResponse givenBidResponseWithError(Map givenUserEids, - List givenEidPermissions, - Map givenAlises, - List expectedExtUserEids) { - // given - final Bidder bidder = mock(Bidder.class); - givenBidder("someBidder", bidder, givenEmptySeatBid()); - final Map bidderToGdpr = singletonMap("someBidder", 1); - - final BidRequest bidRequest = givenBidRequest(givenSingleImp(bidderToGdpr), - builder -> builder - .ext(ExtRequest.of(ExtRequestPrebid.builder() - .aliases(givenAlises) - .data(ExtRequestPrebidData.of(null, givenEidPermissions)) - .build())) - .user(User.builder() - .eids(givenUserEids) - .build())); - - // when - target.holdAuction(givenRequestContext(bidRequest)); - - // then - final ArgumentCaptor bidderRequestCaptor = ArgumentCaptor.forClass(BidderRequest.class); - verify(httpBidderRequester) - .requestBids(any(), bidderRequestCaptor.capture(), any(), any(), any(), any(), anyBoolean()); - final List capturedBidRequests = bidderRequestCaptor.getAllValues(); - assertThat(capturedBidRequests) - .extracting(BidderRequest::getBidRequest) - .extracting(BidRequest::getUser) - .flatExtracting(User::getEids) - .isEqualTo(expectedExtUserEids); - } - private static AppliedToImpl givenAppliedToImpl( Function appliedToImplBuilder) { return appliedToImplBuilder.apply(AppliedToImpl.builder() diff --git a/src/test/java/org/prebid/server/auction/requestfactory/AuctionRequestFactoryTest.java b/src/test/java/org/prebid/server/auction/requestfactory/AuctionRequestFactoryTest.java index 2e30e8d1bc4..59615bdbaab 100644 --- a/src/test/java/org/prebid/server/auction/requestfactory/AuctionRequestFactoryTest.java +++ b/src/test/java/org/prebid/server/auction/requestfactory/AuctionRequestFactoryTest.java @@ -487,7 +487,11 @@ public void shouldReturnFailedFutureIfEidsPermissionsContainsWrongDataType() { final ObjectNode requestNode = mapper.convertValue(bidRequest, ObjectNode.class); final JsonNode eidPermissionNode = mapper.convertValue( - ExtRequestPrebidDataEidPermissions.of("source", emptyList()), JsonNode.class); + ExtRequestPrebidDataEidPermissions.builder() + .source("source") + .bidders(emptyList()) + .build(), + JsonNode.class); requestNode .putObject("ext") @@ -520,7 +524,11 @@ public void shouldReturnFailedFutureIfEidsPermissionsBiddersContainsWrongDataTyp final ObjectNode requestNode = mapper.convertValue(bidRequest, ObjectNode.class); final ObjectNode eidPermissionNode = mapper.convertValue( - ExtRequestPrebidDataEidPermissions.of("source", emptyList()), ObjectNode.class); + ExtRequestPrebidDataEidPermissions.builder() + .source("source") + .bidders(emptyList()) + .build(), + ObjectNode.class); eidPermissionNode.put("bidders", "notArrayValue"); diff --git a/src/test/java/org/prebid/server/validation/RequestValidatorTest.java b/src/test/java/org/prebid/server/validation/RequestValidatorTest.java index e4efd99feb9..653b7bf6a1d 100644 --- a/src/test/java/org/prebid/server/validation/RequestValidatorTest.java +++ b/src/test/java/org/prebid/server/validation/RequestValidatorTest.java @@ -635,7 +635,9 @@ public void validateShouldReturnValidationMessageWhenEidsPermissionsBiddersIsNul final BidRequest bidRequest = validBidRequestBuilder() .ext(ExtRequest.of(ExtRequestPrebid.builder() .data(ExtRequestPrebidData.of(null, - singletonList(ExtRequestPrebidDataEidPermissions.of("source", null)))) + singletonList(ExtRequestPrebidDataEidPermissions.builder() + .source("source") + .build()))) .build())) .build(); @@ -654,7 +656,10 @@ public void validateShouldReturnValidationMessageWhenEidsPermissionsBiddersIsEmp final BidRequest bidRequest = validBidRequestBuilder() .ext(ExtRequest.of(ExtRequestPrebid.builder() .data(ExtRequestPrebidData.of(null, - singletonList(ExtRequestPrebidDataEidPermissions.of("source", emptyList())))) + singletonList(ExtRequestPrebidDataEidPermissions.builder() + .source("source") + .bidders(Collections.emptyList()) + .build()))) .build())) .build(); @@ -675,7 +680,10 @@ public void validateShouldReturnWarningsMessageWhenEidsPermissionsBidderIsNotRec .ext(ExtRequest.of(ExtRequestPrebid.builder() .data(ExtRequestPrebidData.of(null, singletonList( - ExtRequestPrebidDataEidPermissions.of("source", singletonList("bidder1"))))) + ExtRequestPrebidDataEidPermissions.builder() + .source("source") + .bidders(Collections.singletonList("bidder1")) + .build()))) .build())) .build(); @@ -698,7 +706,10 @@ public void validateShouldNotReturnWarningsMessageWhenEidsPermissionsBidderIsNot .ext(ExtRequest.of(ExtRequestPrebid.builder() .data(ExtRequestPrebidData.of(null, singletonList( - ExtRequestPrebidDataEidPermissions.of("source", singletonList("bidder1"))))) + ExtRequestPrebidDataEidPermissions.builder() + .source("source") + .bidders(Collections.singletonList("bidder1")) + .build()))) .build())) .build(); @@ -718,7 +729,10 @@ public void validateShouldReturnWarningMessageWhenEidsPermissionsBidderHasBlankV .ext(ExtRequest.of(ExtRequestPrebid.builder() .data(ExtRequestPrebidData.of(null, singletonList( - ExtRequestPrebidDataEidPermissions.of("source", singletonList(" "))))) + ExtRequestPrebidDataEidPermissions.builder() + .source("source") + .bidders(Collections.singletonList(" ")) + .build()))) .build())) .build(); @@ -744,7 +758,10 @@ public void validateShouldNotReturnValidationErrorWhenBidderIsAlias() { .aliases(singletonMap("bidder1Alias", "bidder1")) .data(ExtRequestPrebidData.of(null, singletonList( - ExtRequestPrebidDataEidPermissions.of("source", singletonList("bidder1"))))) + ExtRequestPrebidDataEidPermissions.builder() + .source("source") + .bidders(Collections.singletonList("bidder1")) + .build()))) .build())) .build(); @@ -762,7 +779,10 @@ public void validateShouldNotReturnValidationErrorWhenBidderIsAsterisk() { .ext(ExtRequest.of(ExtRequestPrebid.builder() .data(ExtRequestPrebidData.of(null, singletonList( - ExtRequestPrebidDataEidPermissions.of("source", singletonList("*"))))) + ExtRequestPrebidDataEidPermissions.builder() + .source("source") + .bidders(Collections.singletonList("*")) + .build()))) .build())) .build(); @@ -780,7 +800,9 @@ public void validateShouldReturnValidationMessageWhenEidsPermissionsHasMissingSo .ext(ExtRequest.of(ExtRequestPrebid.builder() .data(ExtRequestPrebidData.of(null, singletonList( - ExtRequestPrebidDataEidPermissions.of(null, singletonList("bidder1"))))) + ExtRequestPrebidDataEidPermissions.builder() + .bidders(Collections.singletonList("bidder1")) + .build()))) .build())) .build(); @@ -789,7 +811,8 @@ public void validateShouldReturnValidationMessageWhenEidsPermissionsHasMissingSo // then assertThat(result.getErrors()).hasSize(1) - .containsOnly("Missing required value request.ext.prebid.data.eidPermissions[].source"); + .containsOnly("Missing required parameter(s) in request.ext.prebid.data.eidPermissions[]. " + + "Either one or a combination of inserter, source, matcher, or mm should be defined."); } @Test From 52f239008710026ac245d14dd86aaf78fa8cf706 Mon Sep 17 00:00:00 2001 From: Oleksandr Zhevedenko <720803+Net-burst@users.noreply.github.com> Date: Tue, 20 Jan 2026 23:32:36 -0500 Subject: [PATCH 14/38] Prebid Server prepare release 3.38.0 --- extra/bundle/pom.xml | 2 +- extra/modules/confiant-ad-quality/pom.xml | 2 +- extra/modules/fiftyone-devicedetection/pom.xml | 2 +- extra/modules/greenbids-real-time-data/pom.xml | 2 +- extra/modules/live-intent-omni-channel-identity/pom.xml | 2 +- extra/modules/optable-targeting/pom.xml | 2 +- extra/modules/ortb2-blocking/pom.xml | 2 +- extra/modules/pb-request-correction/pom.xml | 2 +- extra/modules/pb-response-correction/pom.xml | 2 +- extra/modules/pb-richmedia-filter/pom.xml | 2 +- extra/modules/pb-rule-engine/pom.xml | 2 +- extra/modules/pom.xml | 2 +- extra/modules/wurfl-devicedetection/pom.xml | 2 +- extra/pom.xml | 4 ++-- pom.xml | 2 +- 15 files changed, 16 insertions(+), 16 deletions(-) diff --git a/extra/bundle/pom.xml b/extra/bundle/pom.xml index ae599096627..3b30bb11bd9 100644 --- a/extra/bundle/pom.xml +++ b/extra/bundle/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.38.0-SNAPSHOT + 3.38.0 ../../extra/pom.xml diff --git a/extra/modules/confiant-ad-quality/pom.xml b/extra/modules/confiant-ad-quality/pom.xml index 4059db432d5..32062723dea 100644 --- a/extra/modules/confiant-ad-quality/pom.xml +++ b/extra/modules/confiant-ad-quality/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0-SNAPSHOT + 3.38.0 confiant-ad-quality diff --git a/extra/modules/fiftyone-devicedetection/pom.xml b/extra/modules/fiftyone-devicedetection/pom.xml index 544033cca2b..d5f9cb6c11b 100644 --- a/extra/modules/fiftyone-devicedetection/pom.xml +++ b/extra/modules/fiftyone-devicedetection/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0-SNAPSHOT + 3.38.0 fiftyone-devicedetection diff --git a/extra/modules/greenbids-real-time-data/pom.xml b/extra/modules/greenbids-real-time-data/pom.xml index 61da977278d..7dd387ea42e 100644 --- a/extra/modules/greenbids-real-time-data/pom.xml +++ b/extra/modules/greenbids-real-time-data/pom.xml @@ -4,7 +4,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0-SNAPSHOT + 3.38.0 greenbids-real-time-data diff --git a/extra/modules/live-intent-omni-channel-identity/pom.xml b/extra/modules/live-intent-omni-channel-identity/pom.xml index f6782ff201d..5607b91d5dc 100644 --- a/extra/modules/live-intent-omni-channel-identity/pom.xml +++ b/extra/modules/live-intent-omni-channel-identity/pom.xml @@ -4,7 +4,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0-SNAPSHOT + 3.38.0 live-intent-omni-channel-identity diff --git a/extra/modules/optable-targeting/pom.xml b/extra/modules/optable-targeting/pom.xml index 03383e72ffe..bbeb0e6e139 100644 --- a/extra/modules/optable-targeting/pom.xml +++ b/extra/modules/optable-targeting/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0-SNAPSHOT + 3.38.0 optable-targeting diff --git a/extra/modules/ortb2-blocking/pom.xml b/extra/modules/ortb2-blocking/pom.xml index d904007722b..524d66d3850 100644 --- a/extra/modules/ortb2-blocking/pom.xml +++ b/extra/modules/ortb2-blocking/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0-SNAPSHOT + 3.38.0 ortb2-blocking diff --git a/extra/modules/pb-request-correction/pom.xml b/extra/modules/pb-request-correction/pom.xml index b4dda3e3c35..7f4b5d8df52 100644 --- a/extra/modules/pb-request-correction/pom.xml +++ b/extra/modules/pb-request-correction/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0-SNAPSHOT + 3.38.0 pb-request-correction diff --git a/extra/modules/pb-response-correction/pom.xml b/extra/modules/pb-response-correction/pom.xml index 0770b766e5b..66644523759 100644 --- a/extra/modules/pb-response-correction/pom.xml +++ b/extra/modules/pb-response-correction/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0-SNAPSHOT + 3.38.0 pb-response-correction diff --git a/extra/modules/pb-richmedia-filter/pom.xml b/extra/modules/pb-richmedia-filter/pom.xml index 6ddd11efcf7..67da38e5ace 100644 --- a/extra/modules/pb-richmedia-filter/pom.xml +++ b/extra/modules/pb-richmedia-filter/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0-SNAPSHOT + 3.38.0 pb-richmedia-filter diff --git a/extra/modules/pb-rule-engine/pom.xml b/extra/modules/pb-rule-engine/pom.xml index e23c6767817..91e0d1b17ea 100644 --- a/extra/modules/pb-rule-engine/pom.xml +++ b/extra/modules/pb-rule-engine/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0-SNAPSHOT + 3.38.0 pb-rule-engine diff --git a/extra/modules/pom.xml b/extra/modules/pom.xml index 6c4e70d62ab..838d72e5ef1 100644 --- a/extra/modules/pom.xml +++ b/extra/modules/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.38.0-SNAPSHOT + 3.38.0 ../../extra/pom.xml diff --git a/extra/modules/wurfl-devicedetection/pom.xml b/extra/modules/wurfl-devicedetection/pom.xml index 555ea37fdce..fe88cfe5376 100644 --- a/extra/modules/wurfl-devicedetection/pom.xml +++ b/extra/modules/wurfl-devicedetection/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0-SNAPSHOT + 3.38.0 wurfl-devicedetection diff --git a/extra/pom.xml b/extra/pom.xml index c41bd328043..c8b7d2c0798 100644 --- a/extra/pom.xml +++ b/extra/pom.xml @@ -4,14 +4,14 @@ org.prebid prebid-server-aggregator - 3.38.0-SNAPSHOT + 3.38.0 pom https://github.com/prebid/prebid-server-java scm:git:git@github.com:prebid/prebid-server-java.git scm:git:git@github.com:prebid/prebid-server-java.git - HEAD + 3.38.0 diff --git a/pom.xml b/pom.xml index 0c79bae025d..f5b8f374cb6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.38.0-SNAPSHOT + 3.38.0 extra/pom.xml From 51f7decb3049490858159b9eb356599d12a40d04 Mon Sep 17 00:00:00 2001 From: Oleksandr Zhevedenko <720803+Net-burst@users.noreply.github.com> Date: Tue, 20 Jan 2026 23:32:36 -0500 Subject: [PATCH 15/38] Prebid Server prepare for next development iteration --- extra/bundle/pom.xml | 2 +- extra/modules/confiant-ad-quality/pom.xml | 2 +- extra/modules/fiftyone-devicedetection/pom.xml | 2 +- extra/modules/greenbids-real-time-data/pom.xml | 2 +- extra/modules/live-intent-omni-channel-identity/pom.xml | 2 +- extra/modules/optable-targeting/pom.xml | 2 +- extra/modules/ortb2-blocking/pom.xml | 2 +- extra/modules/pb-request-correction/pom.xml | 2 +- extra/modules/pb-response-correction/pom.xml | 2 +- extra/modules/pb-richmedia-filter/pom.xml | 2 +- extra/modules/pb-rule-engine/pom.xml | 2 +- extra/modules/pom.xml | 2 +- extra/modules/wurfl-devicedetection/pom.xml | 2 +- extra/pom.xml | 4 ++-- pom.xml | 2 +- 15 files changed, 16 insertions(+), 16 deletions(-) diff --git a/extra/bundle/pom.xml b/extra/bundle/pom.xml index 3b30bb11bd9..5d44255ca2d 100644 --- a/extra/bundle/pom.xml +++ b/extra/bundle/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.38.0 + 3.39.0-SNAPSHOT ../../extra/pom.xml diff --git a/extra/modules/confiant-ad-quality/pom.xml b/extra/modules/confiant-ad-quality/pom.xml index 32062723dea..1d86482129b 100644 --- a/extra/modules/confiant-ad-quality/pom.xml +++ b/extra/modules/confiant-ad-quality/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0 + 3.39.0-SNAPSHOT confiant-ad-quality diff --git a/extra/modules/fiftyone-devicedetection/pom.xml b/extra/modules/fiftyone-devicedetection/pom.xml index d5f9cb6c11b..aafbfa859ac 100644 --- a/extra/modules/fiftyone-devicedetection/pom.xml +++ b/extra/modules/fiftyone-devicedetection/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0 + 3.39.0-SNAPSHOT fiftyone-devicedetection diff --git a/extra/modules/greenbids-real-time-data/pom.xml b/extra/modules/greenbids-real-time-data/pom.xml index 7dd387ea42e..141e4cf087e 100644 --- a/extra/modules/greenbids-real-time-data/pom.xml +++ b/extra/modules/greenbids-real-time-data/pom.xml @@ -4,7 +4,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0 + 3.39.0-SNAPSHOT greenbids-real-time-data diff --git a/extra/modules/live-intent-omni-channel-identity/pom.xml b/extra/modules/live-intent-omni-channel-identity/pom.xml index 5607b91d5dc..c0c6c463354 100644 --- a/extra/modules/live-intent-omni-channel-identity/pom.xml +++ b/extra/modules/live-intent-omni-channel-identity/pom.xml @@ -4,7 +4,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0 + 3.39.0-SNAPSHOT live-intent-omni-channel-identity diff --git a/extra/modules/optable-targeting/pom.xml b/extra/modules/optable-targeting/pom.xml index bbeb0e6e139..9e6503995c0 100644 --- a/extra/modules/optable-targeting/pom.xml +++ b/extra/modules/optable-targeting/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0 + 3.39.0-SNAPSHOT optable-targeting diff --git a/extra/modules/ortb2-blocking/pom.xml b/extra/modules/ortb2-blocking/pom.xml index 524d66d3850..55107ec2223 100644 --- a/extra/modules/ortb2-blocking/pom.xml +++ b/extra/modules/ortb2-blocking/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0 + 3.39.0-SNAPSHOT ortb2-blocking diff --git a/extra/modules/pb-request-correction/pom.xml b/extra/modules/pb-request-correction/pom.xml index 7f4b5d8df52..30fe61d6498 100644 --- a/extra/modules/pb-request-correction/pom.xml +++ b/extra/modules/pb-request-correction/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0 + 3.39.0-SNAPSHOT pb-request-correction diff --git a/extra/modules/pb-response-correction/pom.xml b/extra/modules/pb-response-correction/pom.xml index 66644523759..faf24c6baba 100644 --- a/extra/modules/pb-response-correction/pom.xml +++ b/extra/modules/pb-response-correction/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0 + 3.39.0-SNAPSHOT pb-response-correction diff --git a/extra/modules/pb-richmedia-filter/pom.xml b/extra/modules/pb-richmedia-filter/pom.xml index 67da38e5ace..e8db972c01e 100644 --- a/extra/modules/pb-richmedia-filter/pom.xml +++ b/extra/modules/pb-richmedia-filter/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0 + 3.39.0-SNAPSHOT pb-richmedia-filter diff --git a/extra/modules/pb-rule-engine/pom.xml b/extra/modules/pb-rule-engine/pom.xml index 91e0d1b17ea..0d6d567a809 100644 --- a/extra/modules/pb-rule-engine/pom.xml +++ b/extra/modules/pb-rule-engine/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0 + 3.39.0-SNAPSHOT pb-rule-engine diff --git a/extra/modules/pom.xml b/extra/modules/pom.xml index 838d72e5ef1..e18fe0f665e 100644 --- a/extra/modules/pom.xml +++ b/extra/modules/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.38.0 + 3.39.0-SNAPSHOT ../../extra/pom.xml diff --git a/extra/modules/wurfl-devicedetection/pom.xml b/extra/modules/wurfl-devicedetection/pom.xml index fe88cfe5376..1068dc04cd4 100644 --- a/extra/modules/wurfl-devicedetection/pom.xml +++ b/extra/modules/wurfl-devicedetection/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.38.0 + 3.39.0-SNAPSHOT wurfl-devicedetection diff --git a/extra/pom.xml b/extra/pom.xml index c8b7d2c0798..836df8f4a38 100644 --- a/extra/pom.xml +++ b/extra/pom.xml @@ -4,14 +4,14 @@ org.prebid prebid-server-aggregator - 3.38.0 + 3.39.0-SNAPSHOT pom https://github.com/prebid/prebid-server-java scm:git:git@github.com:prebid/prebid-server-java.git scm:git:git@github.com:prebid/prebid-server-java.git - 3.38.0 + HEAD diff --git a/pom.xml b/pom.xml index f5b8f374cb6..b9f4a2eff8b 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.38.0 + 3.39.0-SNAPSHOT extra/pom.xml From 535e8118764804b37fff50f890e8434eea6a1f43 Mon Sep 17 00:00:00 2001 From: markiian Date: Tue, 27 Jan 2026 14:05:58 +0200 Subject: [PATCH 16/38] Add coverage for allow duplicate cookie family names --- .../functional/model/bidder/BidderName.groovy | 1 + .../server/functional/tests/SetUidSpec.groovy | 45 ++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy b/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy index 702918fa886..b852fbb4343 100644 --- a/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy @@ -30,6 +30,7 @@ enum BidderName { AMX("amx"), AMX_CAMEL_CASE("AmX"), AMX_UPPER_CASE("AMX"), + ADTRGTME("adtrgtme") @JsonValue final String value diff --git a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy index 7e9eff9ebd3..f4f77fc611e 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy @@ -9,12 +9,14 @@ import org.prebid.server.functional.service.PrebidServerService import org.prebid.server.functional.util.PBSUtils import org.prebid.server.functional.util.privacy.TcfConsent import org.prebid.server.util.ResourceUtil +import spock.lang.IgnoreRest import spock.lang.Shared import java.time.Clock import java.time.ZonedDateTime import java.time.temporal.ChronoUnit +import static org.prebid.server.functional.model.bidder.BidderName.ADTRGTME import static org.prebid.server.functional.model.bidder.BidderName.ALIAS import static org.prebid.server.functional.model.bidder.BidderName.ALIAS_CAMEL_CASE import static org.prebid.server.functional.model.bidder.BidderName.APPNEXUS @@ -38,21 +40,36 @@ class SetUidSpec extends BaseSpec { private static final boolean CORS_SUPPORT = false private static final Integer RANDOM_EXPIRE_DAY = PBSUtils.getRandomNumber(1, 10) private static final String USER_SYNC_URL = "$networkServiceContainer.rootUri/generic-usersync" + private static final String GENERIC_COOKIE_FAMILY_NAME = GENERIC.value + private static final String VENDOR_ID = PBSUtils.randomNumber as String + private static final Map UID_COOKIES_CONFIG = ['setuid.number-of-uid-cookies': MAX_NUMBER_OF_UID_COOKIES.toString()] + private static final Map GENERIC_ALIAS_CONFIG = ["adapters.generic.aliases.alias.enabled" : "true", + "adapters.generic.aliases.alias.endpoint": "$networkServiceContainer.rootUri/auction".toString()] + private static final String TCF_ERROR_MESSAGE = "The gdpr_consent param prevents cookies from being saved" + private static final int UNAVAILABLE_FOR_LEGAL_REASONS_CODE = 451 private static final Map PBS_CONFIG = ["host-cookie.max-cookie-size-bytes" : MAX_COOKIE_SIZE as String, + "adapters.${RUBICON.value}.enabled" : "true", "adapters.${RUBICON.value}.usersync.cookie-family-name" : RUBICON.value, + "adapters.${OPENX.value}.enabled" : "true", "adapters.${OPENX.value}.usersync.cookie-family-name" : OPENX.value, + "adapters.${APPNEXUS.value}.enabled" : "true", "adapters.${APPNEXUS.value}.usersync.cookie-family-name" : APPNEXUS.value, + + "adapters.${GENERIC.value}.meta-info.vendor-id" : VENDOR_ID, + "adapters.${GENERIC.value}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, - "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors": CORS_SUPPORT.toString()] - private static final Map UID_COOKIES_CONFIG = ['setuid.number-of-uid-cookies': MAX_NUMBER_OF_UID_COOKIES.toString()] - private static final Map GENERIC_ALIAS_CONFIG = ["adapters.generic.aliases.alias.enabled" : "true", - "adapters.generic.aliases.alias.endpoint": "$networkServiceContainer.rootUri/auction".toString()] - private static final String TCF_ERROR_MESSAGE = "The gdpr_consent param prevents cookies from being saved" - private static final int UNAVAILABLE_FOR_LEGAL_REASONS_CODE = 451 + "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors": CORS_SUPPORT.toString(), + + "adapters.${ADTRGTME}.enabled" : "true", + "adapters.${ADTRGTME}.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), + "adapters.${ADTRGTME}.meta-info.vendor-id" : VENDOR_ID, + "adapters.${ADTRGTME}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, + "adapters.${ADTRGTME}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, + "adapters.${ADTRGTME}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString()] @Shared PrebidServerService singleCookiesPbsService = pbsServiceFactory.getService(PBS_CONFIG + GENERIC_ALIAS_CONFIG) @@ -513,6 +530,22 @@ class SetUidSpec extends BaseSpec { assert getSetUidsHeaders(response, true).size() == MAX_NUMBER_OF_UID_COOKIES } + def "PBS shouldn't failed with error when adapters has same user sync and vendor id config"() { + given: "Default set uid request" + def request = SetuidRequest.defaultSetuidRequest + + and: "Default uids cookie generic and adtrgtme" + def genericUidsCookie = UidsCookie.getDefaultUidsCookie(GENERIC) + def adtrgtmeUidsCookie = UidsCookie.getDefaultUidsCookie(ADTRGTME) + + when: "PBS processes auction request" + def response = singleCookiesPbsService.sendSetUidRequest(request, [genericUidsCookie, adtrgtmeUidsCookie]) + + then: "Response should contain requested tempUIDs" + assert response.uidsCookie.tempUIDs[GENERIC] + assert response.uidsCookie.tempUIDs[ADTRGTME] + } + List getSetUidsHeaders(SetuidResponse response, boolean includeEmpty = false) { response.headers.get("Set-Cookie").findAll { cookie -> includeEmpty || !(cookie =~ /\buids\d*=\s*;/) From b1bd6aa0d3e99750fc206f2f2fe18c976a911757 Mon Sep 17 00:00:00 2001 From: markiian Date: Tue, 27 Jan 2026 14:07:05 +0200 Subject: [PATCH 17/38] Remove redundant imports --- .../groovy/org/prebid/server/functional/tests/SetUidSpec.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy index f4f77fc611e..2e2f4b10f35 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy @@ -9,7 +9,6 @@ import org.prebid.server.functional.service.PrebidServerService import org.prebid.server.functional.util.PBSUtils import org.prebid.server.functional.util.privacy.TcfConsent import org.prebid.server.util.ResourceUtil -import spock.lang.IgnoreRest import spock.lang.Shared import java.time.Clock From eaffb588b11e1d6e29a7c40e4edbf66393e695db Mon Sep 17 00:00:00 2001 From: markiian Date: Tue, 27 Jan 2026 15:13:26 +0200 Subject: [PATCH 18/38] Add coverage for aliases --- .../functional/model/bidder/BidderName.groovy | 4 +- .../server/functional/tests/SetUidSpec.groovy | 72 ++++++++++++++----- 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy b/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy index b852fbb4343..6a03d7a7ca7 100644 --- a/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy @@ -30,7 +30,9 @@ enum BidderName { AMX("amx"), AMX_CAMEL_CASE("AmX"), AMX_UPPER_CASE("AMX"), - ADTRGTME("adtrgtme") + ADTRGTME("adtrgtme"), + BLUE("blue"), + CWIRE("cwire") @JsonValue final String value diff --git a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy index 2e2f4b10f35..08001163ac8 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy @@ -1,6 +1,7 @@ package org.prebid.server.functional.tests import org.prebid.server.functional.model.UidsCookie +import org.prebid.server.functional.model.bidder.BidderName import org.prebid.server.functional.model.request.setuid.SetuidRequest import org.prebid.server.functional.model.response.cookiesync.UserSyncInfo import org.prebid.server.functional.model.response.setuid.SetuidResponse @@ -9,16 +10,19 @@ import org.prebid.server.functional.service.PrebidServerService import org.prebid.server.functional.util.PBSUtils import org.prebid.server.functional.util.privacy.TcfConsent import org.prebid.server.util.ResourceUtil +import spock.lang.IgnoreRest import spock.lang.Shared import java.time.Clock import java.time.ZonedDateTime import java.time.temporal.ChronoUnit +import static org.prebid.server.functional.model.bidder.BidderName.* import static org.prebid.server.functional.model.bidder.BidderName.ADTRGTME import static org.prebid.server.functional.model.bidder.BidderName.ALIAS import static org.prebid.server.functional.model.bidder.BidderName.ALIAS_CAMEL_CASE import static org.prebid.server.functional.model.bidder.BidderName.APPNEXUS +import static org.prebid.server.functional.model.bidder.BidderName.BLUE import static org.prebid.server.functional.model.bidder.BidderName.GENERIC import static org.prebid.server.functional.model.bidder.BidderName.GENERIC_CAMEL_CASE import static org.prebid.server.functional.model.bidder.BidderName.OPENX @@ -47,28 +51,44 @@ class SetUidSpec extends BaseSpec { private static final String TCF_ERROR_MESSAGE = "The gdpr_consent param prevents cookies from being saved" private static final int UNAVAILABLE_FOR_LEGAL_REASONS_CODE = 451 private static final Map PBS_CONFIG = - ["host-cookie.max-cookie-size-bytes" : MAX_COOKIE_SIZE as String, + ["host-cookie.max-cookie-size-bytes" : MAX_COOKIE_SIZE as String, - "adapters.${RUBICON.value}.enabled" : "true", - "adapters.${RUBICON.value}.usersync.cookie-family-name" : RUBICON.value, + "adapters.${RUBICON.value}.enabled" : "true", + "adapters.${RUBICON.value}.usersync.cookie-family-name" : RUBICON.value, - "adapters.${OPENX.value}.enabled" : "true", - "adapters.${OPENX.value}.usersync.cookie-family-name" : OPENX.value, + "adapters.${OPENX.value}.enabled" : "true", + "adapters.${OPENX.value}.usersync.cookie-family-name" : OPENX.value, - "adapters.${APPNEXUS.value}.enabled" : "true", - "adapters.${APPNEXUS.value}.usersync.cookie-family-name" : APPNEXUS.value, + "adapters.${APPNEXUS.value}.enabled" : "true", + "adapters.${APPNEXUS.value}.usersync.cookie-family-name" : APPNEXUS.value, - "adapters.${GENERIC.value}.meta-info.vendor-id" : VENDOR_ID, - "adapters.${GENERIC.value}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, - "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, - "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors": CORS_SUPPORT.toString(), + "adapters.${GENERIC.value}.meta-info.vendor-id" : VENDOR_ID, + "adapters.${GENERIC.value}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, + "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, + "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString(), - "adapters.${ADTRGTME}.enabled" : "true", - "adapters.${ADTRGTME}.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), - "adapters.${ADTRGTME}.meta-info.vendor-id" : VENDOR_ID, - "adapters.${ADTRGTME}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, - "adapters.${ADTRGTME}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, - "adapters.${ADTRGTME}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString()] + "adapters.${ADTRGTME}.enabled" : "true", + "adapters.${ADTRGTME}.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), + "adapters.${ADTRGTME}.meta-info.vendor-id" : VENDOR_ID, + "adapters.${ADTRGTME}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, + "adapters.${ADTRGTME}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, + "adapters.${ADTRGTME}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString(), + + "adapters.${GENERIC}.aliases.${BLUE}.enabled" : "true", + "adapters.${GENERIC}.aliases.${BLUE}.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), + "adapters.${GENERIC}.aliases.${BLUE}.meta-info.vendor-id" : VENDOR_ID, + "adapters.${GENERIC}.aliases.${BLUE}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, + "adapters.${GENERIC}.aliases.${BLUE}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, + "adapters.${GENERIC}.aliases.${BLUE}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString(), + + "adapters.${GENERIC}.aliases.${CWIRE}.enabled" : "true", + "adapters.${GENERIC}.aliases.${CWIRE}.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), + "adapters.${GENERIC}.aliases.${CWIRE}.meta-info.vendor-id" : VENDOR_ID, + "adapters.${GENERIC}.aliases.${CWIRE}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, + "adapters.${GENERIC}.aliases.${CWIRE}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, + "adapters.${GENERIC}.aliases.${CWIRE}.usersync.${USER_SYNC_TYPE.value}.support-cors": CORS_SUPPORT.toString() + + ] @Shared PrebidServerService singleCookiesPbsService = pbsServiceFactory.getService(PBS_CONFIG + GENERIC_ALIAS_CONFIG) @@ -538,13 +558,29 @@ class SetUidSpec extends BaseSpec { def adtrgtmeUidsCookie = UidsCookie.getDefaultUidsCookie(ADTRGTME) when: "PBS processes auction request" - def response = singleCookiesPbsService.sendSetUidRequest(request, [genericUidsCookie, adtrgtmeUidsCookie]) + def response = singleCookiesPbsService.sendSetUidRequest(request, [adtrgtmeUidsCookie, genericUidsCookie]) then: "Response should contain requested tempUIDs" assert response.uidsCookie.tempUIDs[GENERIC] assert response.uidsCookie.tempUIDs[ADTRGTME] } + def "PBS shouldn't failed with error when alias adapters has same user sync and vendor id config"() { + given: "Default set uid request" + def request = SetuidRequest.defaultSetuidRequest + + and: "Default uids cookie cwire and blue" + def cwireUidsCookie = UidsCookie.getDefaultUidsCookie(CWIRE) + def blueUidsCookie = UidsCookie.getDefaultUidsCookie(BLUE) + + when: "PBS processes auction request" + def response = singleCookiesPbsService.sendSetUidRequest(request, [cwireUidsCookie, blueUidsCookie]) + + then: "Response should contain requested tempUIDs" + assert response.uidsCookie.tempUIDs[CWIRE] + assert response.uidsCookie.tempUIDs[BLUE] + } + List getSetUidsHeaders(SetuidResponse response, boolean includeEmpty = false) { response.headers.get("Set-Cookie").findAll { cookie -> includeEmpty || !(cookie =~ /\buids\d*=\s*;/) From 1f89ec173a0bc69c5ec4fb186843410c5ec12c7b Mon Sep 17 00:00:00 2001 From: markiian Date: Wed, 28 Jan 2026 16:49:31 +0200 Subject: [PATCH 19/38] Update after review --- .../org/prebid/server/functional/tests/SetUidSpec.groovy | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy index 08001163ac8..932df529e95 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy @@ -1,7 +1,6 @@ package org.prebid.server.functional.tests import org.prebid.server.functional.model.UidsCookie -import org.prebid.server.functional.model.bidder.BidderName import org.prebid.server.functional.model.request.setuid.SetuidRequest import org.prebid.server.functional.model.response.cookiesync.UserSyncInfo import org.prebid.server.functional.model.response.setuid.SetuidResponse @@ -10,14 +9,12 @@ import org.prebid.server.functional.service.PrebidServerService import org.prebid.server.functional.util.PBSUtils import org.prebid.server.functional.util.privacy.TcfConsent import org.prebid.server.util.ResourceUtil -import spock.lang.IgnoreRest import spock.lang.Shared import java.time.Clock import java.time.ZonedDateTime import java.time.temporal.ChronoUnit -import static org.prebid.server.functional.model.bidder.BidderName.* import static org.prebid.server.functional.model.bidder.BidderName.ADTRGTME import static org.prebid.server.functional.model.bidder.BidderName.ALIAS import static org.prebid.server.functional.model.bidder.BidderName.ALIAS_CAMEL_CASE @@ -29,6 +26,7 @@ import static org.prebid.server.functional.model.bidder.BidderName.OPENX import static org.prebid.server.functional.model.bidder.BidderName.RUBICON import static org.prebid.server.functional.model.bidder.BidderName.UNKNOWN import static org.prebid.server.functional.model.bidder.BidderName.WILDCARD +import static org.prebid.server.functional.model.bidder.BidderName.CWIRE import static org.prebid.server.functional.model.request.setuid.UidWithExpiry.defaultUidWithExpiry import static org.prebid.server.functional.model.response.cookiesync.UserSyncInfo.Type.REDIRECT import static org.prebid.server.functional.testcontainers.Dependencies.networkServiceContainer From 2360fb183451b38e87bf6410fb6ced68b24160ac Mon Sep 17 00:00:00 2001 From: markiian Date: Thu, 29 Jan 2026 13:38:04 +0200 Subject: [PATCH 20/38] Update after review --- .../functional/model/bidder/BidderName.groovy | 3 - .../server/functional/tests/SetUidSpec.groovy | 97 ++++++++++--------- 2 files changed, 49 insertions(+), 51 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy b/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy index 6a03d7a7ca7..702918fa886 100644 --- a/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/bidder/BidderName.groovy @@ -30,9 +30,6 @@ enum BidderName { AMX("amx"), AMX_CAMEL_CASE("AmX"), AMX_UPPER_CASE("AMX"), - ADTRGTME("adtrgtme"), - BLUE("blue"), - CWIRE("cwire") @JsonValue final String value diff --git a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy index 932df529e95..f931266d0b5 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy @@ -15,18 +15,19 @@ import java.time.Clock import java.time.ZonedDateTime import java.time.temporal.ChronoUnit -import static org.prebid.server.functional.model.bidder.BidderName.ADTRGTME +import static org.prebid.server.functional.model.bidder.BidderName.ACEEX import static org.prebid.server.functional.model.bidder.BidderName.ALIAS import static org.prebid.server.functional.model.bidder.BidderName.ALIAS_CAMEL_CASE +import static org.prebid.server.functional.model.bidder.BidderName.ALIAS_UPPER_CASE import static org.prebid.server.functional.model.bidder.BidderName.APPNEXUS -import static org.prebid.server.functional.model.bidder.BidderName.BLUE import static org.prebid.server.functional.model.bidder.BidderName.GENERIC import static org.prebid.server.functional.model.bidder.BidderName.GENERIC_CAMEL_CASE +import static org.prebid.server.functional.model.bidder.BidderName.GRID import static org.prebid.server.functional.model.bidder.BidderName.OPENX +import static org.prebid.server.functional.model.bidder.BidderName.OPENX_ALIAS import static org.prebid.server.functional.model.bidder.BidderName.RUBICON import static org.prebid.server.functional.model.bidder.BidderName.UNKNOWN import static org.prebid.server.functional.model.bidder.BidderName.WILDCARD -import static org.prebid.server.functional.model.bidder.BidderName.CWIRE import static org.prebid.server.functional.model.request.setuid.UidWithExpiry.defaultUidWithExpiry import static org.prebid.server.functional.model.response.cookiesync.UserSyncInfo.Type.REDIRECT import static org.prebid.server.functional.testcontainers.Dependencies.networkServiceContainer @@ -49,42 +50,42 @@ class SetUidSpec extends BaseSpec { private static final String TCF_ERROR_MESSAGE = "The gdpr_consent param prevents cookies from being saved" private static final int UNAVAILABLE_FOR_LEGAL_REASONS_CODE = 451 private static final Map PBS_CONFIG = - ["host-cookie.max-cookie-size-bytes" : MAX_COOKIE_SIZE as String, - - "adapters.${RUBICON.value}.enabled" : "true", - "adapters.${RUBICON.value}.usersync.cookie-family-name" : RUBICON.value, - - "adapters.${OPENX.value}.enabled" : "true", - "adapters.${OPENX.value}.usersync.cookie-family-name" : OPENX.value, - - "adapters.${APPNEXUS.value}.enabled" : "true", - "adapters.${APPNEXUS.value}.usersync.cookie-family-name" : APPNEXUS.value, - - "adapters.${GENERIC.value}.meta-info.vendor-id" : VENDOR_ID, - "adapters.${GENERIC.value}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, - "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, - "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString(), - - "adapters.${ADTRGTME}.enabled" : "true", - "adapters.${ADTRGTME}.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), - "adapters.${ADTRGTME}.meta-info.vendor-id" : VENDOR_ID, - "adapters.${ADTRGTME}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, - "adapters.${ADTRGTME}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, - "adapters.${ADTRGTME}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString(), - - "adapters.${GENERIC}.aliases.${BLUE}.enabled" : "true", - "adapters.${GENERIC}.aliases.${BLUE}.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), - "adapters.${GENERIC}.aliases.${BLUE}.meta-info.vendor-id" : VENDOR_ID, - "adapters.${GENERIC}.aliases.${BLUE}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, - "adapters.${GENERIC}.aliases.${BLUE}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, - "adapters.${GENERIC}.aliases.${BLUE}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString(), - - "adapters.${GENERIC}.aliases.${CWIRE}.enabled" : "true", - "adapters.${GENERIC}.aliases.${CWIRE}.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), - "adapters.${GENERIC}.aliases.${CWIRE}.meta-info.vendor-id" : VENDOR_ID, - "adapters.${GENERIC}.aliases.${CWIRE}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, - "adapters.${GENERIC}.aliases.${CWIRE}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, - "adapters.${GENERIC}.aliases.${CWIRE}.usersync.${USER_SYNC_TYPE.value}.support-cors": CORS_SUPPORT.toString() + ["host-cookie.max-cookie-size-bytes" : MAX_COOKIE_SIZE as String, + + "adapters.${RUBICON.value}.enabled" : "true", + "adapters.${RUBICON.value}.usersync.cookie-family-name" : RUBICON.value, + + "adapters.${OPENX.value}.enabled" : "true", + "adapters.${OPENX.value}.usersync.cookie-family-name" : OPENX.value, + + "adapters.${APPNEXUS.value}.enabled" : "true", + "adapters.${APPNEXUS.value}.usersync.cookie-family-name" : APPNEXUS.value, + + "adapters.${GENERIC.value}.meta-info.vendor-id" : VENDOR_ID, + "adapters.${GENERIC.value}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, + "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, + "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString(), + + "adapters.${ACEEX}.enabled" : "true", + "adapters.${ACEEX}.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), + "adapters.${ACEEX}.meta-info.vendor-id" : VENDOR_ID, + "adapters.${ACEEX}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, + "adapters.${ACEEX}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, + "adapters.${ACEEX}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString(), + + "adapters.${GENERIC}.aliases.${ALIAS}.enabled" : "true", + "adapters.${GENERIC}.aliases.${ALIAS}.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), + "adapters.${GENERIC}.aliases.${ALIAS}.meta-info.vendor-id" : VENDOR_ID, + "adapters.${GENERIC}.aliases.${ALIAS}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, + "adapters.${GENERIC}.aliases.${ALIAS}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, + "adapters.${GENERIC}.aliases.${ALIAS}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString(), + + "adapters.${GENERIC}.aliases.${OPENX_ALIAS}.enabled" : "true", + "adapters.${GENERIC}.aliases.${OPENX_ALIAS}.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), + "adapters.${GENERIC}.aliases.${OPENX_ALIAS}.meta-info.vendor-id" : VENDOR_ID, + "adapters.${GENERIC}.aliases.${OPENX_ALIAS}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, + "adapters.${GENERIC}.aliases.${OPENX_ALIAS}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, + "adapters.${GENERIC}.aliases.${OPENX_ALIAS}.usersync.${USER_SYNC_TYPE.value}.support-cors": CORS_SUPPORT.toString() ] @@ -553,30 +554,30 @@ class SetUidSpec extends BaseSpec { and: "Default uids cookie generic and adtrgtme" def genericUidsCookie = UidsCookie.getDefaultUidsCookie(GENERIC) - def adtrgtmeUidsCookie = UidsCookie.getDefaultUidsCookie(ADTRGTME) + def gridUidsCookie = UidsCookie.getDefaultUidsCookie(GRID) when: "PBS processes auction request" - def response = singleCookiesPbsService.sendSetUidRequest(request, [adtrgtmeUidsCookie, genericUidsCookie]) + def response = singleCookiesPbsService.sendSetUidRequest(request, [gridUidsCookie, genericUidsCookie]) then: "Response should contain requested tempUIDs" assert response.uidsCookie.tempUIDs[GENERIC] - assert response.uidsCookie.tempUIDs[ADTRGTME] + assert response.uidsCookie.tempUIDs[GRID] } def "PBS shouldn't failed with error when alias adapters has same user sync and vendor id config"() { given: "Default set uid request" def request = SetuidRequest.defaultSetuidRequest - and: "Default uids cookie cwire and blue" - def cwireUidsCookie = UidsCookie.getDefaultUidsCookie(CWIRE) - def blueUidsCookie = UidsCookie.getDefaultUidsCookie(BLUE) + and: "Default uids cookie generic alias and opnex alias" + def genericAliasUidsCookie = UidsCookie.getDefaultUidsCookie(ALIAS) + def genericOpenxAliasUidsCookie = UidsCookie.getDefaultUidsCookie(OPENX_ALIAS) when: "PBS processes auction request" - def response = singleCookiesPbsService.sendSetUidRequest(request, [cwireUidsCookie, blueUidsCookie]) + def response = singleCookiesPbsService.sendSetUidRequest(request, [genericAliasUidsCookie, genericOpenxAliasUidsCookie]) then: "Response should contain requested tempUIDs" - assert response.uidsCookie.tempUIDs[CWIRE] - assert response.uidsCookie.tempUIDs[BLUE] + assert response.uidsCookie.tempUIDs[ALIAS] + assert response.uidsCookie.tempUIDs[OPENX_ALIAS] } List getSetUidsHeaders(SetuidResponse response, boolean includeEmpty = false) { From 58f0fcdbde7c3cc96b9a00071c051c0aeb94ac13 Mon Sep 17 00:00:00 2001 From: markiian Date: Fri, 30 Jan 2026 01:27:25 +0200 Subject: [PATCH 21/38] Last updated --- .../org/prebid/server/functional/tests/SetUidSpec.groovy | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy index f931266d0b5..88771ee3a8b 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy @@ -15,10 +15,8 @@ import java.time.Clock import java.time.ZonedDateTime import java.time.temporal.ChronoUnit -import static org.prebid.server.functional.model.bidder.BidderName.ACEEX import static org.prebid.server.functional.model.bidder.BidderName.ALIAS import static org.prebid.server.functional.model.bidder.BidderName.ALIAS_CAMEL_CASE -import static org.prebid.server.functional.model.bidder.BidderName.ALIAS_UPPER_CASE import static org.prebid.server.functional.model.bidder.BidderName.APPNEXUS import static org.prebid.server.functional.model.bidder.BidderName.GENERIC import static org.prebid.server.functional.model.bidder.BidderName.GENERIC_CAMEL_CASE @@ -66,13 +64,6 @@ class SetUidSpec extends BaseSpec { "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString(), - "adapters.${ACEEX}.enabled" : "true", - "adapters.${ACEEX}.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), - "adapters.${ACEEX}.meta-info.vendor-id" : VENDOR_ID, - "adapters.${ACEEX}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, - "adapters.${ACEEX}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, - "adapters.${ACEEX}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString(), - "adapters.${GENERIC}.aliases.${ALIAS}.enabled" : "true", "adapters.${GENERIC}.aliases.${ALIAS}.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), "adapters.${GENERIC}.aliases.${ALIAS}.meta-info.vendor-id" : VENDOR_ID, From 877978f41f41154f53946b5d6d87b36379a0c473 Mon Sep 17 00:00:00 2001 From: markiian Date: Tue, 3 Feb 2026 14:06:59 +0200 Subject: [PATCH 22/38] Revert config --- .../org/prebid/server/functional/tests/SetUidSpec.groovy | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy index 88771ee3a8b..92b457414a4 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy @@ -64,6 +64,12 @@ class SetUidSpec extends BaseSpec { "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString(), + "adapters.${GRID.value}.enabled" : "true", + "adapters.${GRID.value}.meta-info.vendor-id" : VENDOR_ID, + "adapters.${GRID.value}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, + "adapters.${GRID.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, + "adapters.${GRID.value}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString(), + "adapters.${GENERIC}.aliases.${ALIAS}.enabled" : "true", "adapters.${GENERIC}.aliases.${ALIAS}.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), "adapters.${GENERIC}.aliases.${ALIAS}.meta-info.vendor-id" : VENDOR_ID, From db42223819c8334d8f0849f99d2a10a31318f2e5 Mon Sep 17 00:00:00 2001 From: Viktor Kryshtal <33136089+Lightwood13@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:29:22 +0200 Subject: [PATCH 23/38] Nexx360: Preserve custom imp.ext fields and add vendor id (#4359) --- .../server/bidder/nexx360/Nexx360Bidder.java | 5 +++- src/main/resources/bidder-config/nexx360.yaml | 2 +- .../bidder/nexx360/Nexx360BidderTest.java | 25 +++++++++++++++++++ .../easybid/test-easybid-bid-request.json | 1 + .../nexx360/test-nexx360-bid-request.json | 1 + .../oneaccord/test-1accord-bid-request.json | 1 + .../prismassp/test-prismassp-bid-request.json | 1 + 7 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/nexx360/Nexx360Bidder.java b/src/main/java/org/prebid/server/bidder/nexx360/Nexx360Bidder.java index 180f545bf47..b4b643cc3eb 100644 --- a/src/main/java/org/prebid/server/bidder/nexx360/Nexx360Bidder.java +++ b/src/main/java/org/prebid/server/bidder/nexx360/Nexx360Bidder.java @@ -1,6 +1,7 @@ package org.prebid.server.bidder.nexx360; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; import com.iab.openrtb.response.Bid; @@ -81,7 +82,9 @@ private ExtImpNexx360 parseImpExt(Imp imp) { private Imp modifyImp(Imp imp) { return imp.toBuilder() - .ext(mapper.mapper().createObjectNode().set(BIDDER_NAME, imp.getExt().get("bidder"))) + .ext(imp.getExt().deepCopy() + .without("bidder") + .set(BIDDER_NAME, imp.getExt().get("bidder"))) .build(); } diff --git a/src/main/resources/bidder-config/nexx360.yaml b/src/main/resources/bidder-config/nexx360.yaml index 443ca7f5ab4..975dcd6a21e 100644 --- a/src/main/resources/bidder-config/nexx360.yaml +++ b/src/main/resources/bidder-config/nexx360.yaml @@ -19,4 +19,4 @@ adapters: - native - audio supported-vendors: - vendor-id: 0 + vendor-id: 965 diff --git a/src/test/java/org/prebid/server/bidder/nexx360/Nexx360BidderTest.java b/src/test/java/org/prebid/server/bidder/nexx360/Nexx360BidderTest.java index 09c50589e19..bb06e21635b 100644 --- a/src/test/java/org/prebid/server/bidder/nexx360/Nexx360BidderTest.java +++ b/src/test/java/org/prebid/server/bidder/nexx360/Nexx360BidderTest.java @@ -28,6 +28,7 @@ import java.math.BigDecimal; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.function.UnaryOperator; import static java.util.Collections.singletonList; @@ -133,6 +134,30 @@ public void makeHttpRequestsShouldModifyImpExt() { .containsExactly(expectedExt1, expectedExt2); } + @Test + public void makeHttpRequestsShouldPreserveCustomFieldsInImpExt() { + // given + final BidRequest bidRequest = givenBidRequest( + imp -> imp.id("imp1").ext(mapper.valueToTree(Map.of( + "bidder", ExtImpNexx360.of("tag1", "p1"), + "customField", "customValue")))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + final ObjectNode expectedExt = mapper.valueToTree(Map.of( + "nexx360", ExtImpNexx360.of("tag1", "p1"), + "customField", "customValue")); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getExt) + .containsExactly(expectedExt); + } + @Test public void makeHttpRequestsShouldModifyRequestExt() { // given diff --git a/src/test/resources/org/prebid/server/it/openrtb2/easybid/test-easybid-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/easybid/test-easybid-bid-request.json index a6a87d4012c..94c35c9da4d 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/easybid/test-easybid-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/easybid/test-easybid-bid-request.json @@ -9,6 +9,7 @@ }, "secure": 1, "ext": { + "tid": "${json-unit.any-string}", "nexx360": { "placement": "placement" } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/nexx360/test-nexx360-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/nexx360/test-nexx360-bid-request.json index a6a87d4012c..94c35c9da4d 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/nexx360/test-nexx360-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/nexx360/test-nexx360-bid-request.json @@ -9,6 +9,7 @@ }, "secure": 1, "ext": { + "tid": "${json-unit.any-string}", "nexx360": { "placement": "placement" } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/oneaccord/test-1accord-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/oneaccord/test-1accord-bid-request.json index a6a87d4012c..94c35c9da4d 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/oneaccord/test-1accord-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/oneaccord/test-1accord-bid-request.json @@ -9,6 +9,7 @@ }, "secure": 1, "ext": { + "tid": "${json-unit.any-string}", "nexx360": { "placement": "placement" } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/prismassp/test-prismassp-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/prismassp/test-prismassp-bid-request.json index a6a87d4012c..94c35c9da4d 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/prismassp/test-prismassp-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/prismassp/test-prismassp-bid-request.json @@ -9,6 +9,7 @@ }, "secure": 1, "ext": { + "tid": "${json-unit.any-string}", "nexx360": { "placement": "placement" } From 4d9af22ecf0269c188ca69d33f7123ffc363ff96 Mon Sep 17 00:00:00 2001 From: przemkaczmarek <167743744+przemkaczmarek@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:30:54 +0100 Subject: [PATCH 24/38] New Adapter: RadiantFusion - Attekmi alias (#4365) --- .../resources/bidder-config/smarthub.yaml | 3 + .../prebid/server/it/RadianfusionTest.java | 38 ++++++++++++ .../test-auction-radianfusion-request.json | 25 ++++++++ .../test-auction-radianfusion-response.json | 43 ++++++++++++++ .../test-radianfusion-bid-request.json | 58 +++++++++++++++++++ .../test-radianfusion-bid-response.json | 23 ++++++++ .../server/it/test-application.properties | 2 + 7 files changed, 192 insertions(+) create mode 100644 src/test/java/org/prebid/server/it/RadianfusionTest.java create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/radianfusion/test-auction-radianfusion-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/radianfusion/test-auction-radianfusion-response.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/radianfusion/test-radianfusion-bid-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/radianfusion/test-radianfusion-bid-response.json diff --git a/src/main/resources/bidder-config/smarthub.yaml b/src/main/resources/bidder-config/smarthub.yaml index 73a50212676..d9441f1c0f3 100644 --- a/src/main/resources/bidder-config/smarthub.yaml +++ b/src/main/resources/bidder-config/smarthub.yaml @@ -29,6 +29,9 @@ adapters: addigi: enabled: false endpoint: https://addigi-prebid.attekmi.com/pbserver/?seat={{AccountID}}&token={{SourceId}} + radianfusion: + enabled: false + endpoint: https://radiantfusion-prebid.attekmi.co/pbserver/?seat={{AccountID}}&token={{SourceId}} meta-info: maintainer-email: prebid@attekmi.com app-media-types: diff --git a/src/test/java/org/prebid/server/it/RadianfusionTest.java b/src/test/java/org/prebid/server/it/RadianfusionTest.java new file mode 100644 index 00000000000..52b401de7b3 --- /dev/null +++ b/src/test/java/org/prebid/server/it/RadianfusionTest.java @@ -0,0 +1,38 @@ +package org.prebid.server.it; + +import io.restassured.response.Response; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.prebid.server.model.Endpoint; + +import java.io.IOException; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static java.util.Collections.singletonList; + +public class RadianfusionTest extends IntegrationTest { + + @Test + public void openrtb2AuctionShouldRespondWithBidsFromRadianfusion() throws IOException, JSONException { + // given + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/radianfusion-exchange")) + .withQueryParam("host", equalTo("someUniquePartnerName")) + .withQueryParam("accountId", equalTo("someSeat")) + .withQueryParam("sourceId", equalTo("someToken")) + .withRequestBody(equalToJson(jsonFrom("openrtb2/radianfusion/test-radianfusion-bid-request.json"))) + .willReturn(aResponse().withBody( + jsonFrom("openrtb2/radianfusion/test-radianfusion-bid-response.json")))); + + // when + final Response response = responseFor("openrtb2/radianfusion/test-auction-radianfusion-request.json", + Endpoint.openrtb2_auction); + + // then + assertJsonEquals("openrtb2/radianfusion/test-auction-radianfusion-response.json", + response, singletonList("radianfusion")); + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/radianfusion/test-auction-radianfusion-request.json b/src/test/resources/org/prebid/server/it/openrtb2/radianfusion/test-auction-radianfusion-request.json new file mode 100644 index 00000000000..c4c23330095 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/radianfusion/test-auction-radianfusion-request.json @@ -0,0 +1,25 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "radianfusion": { + "partnerName": "someUniquePartnerName", + "seat": "someSeat", + "token": "someToken" + } + } + } + ], + "tmax": 5000, + "regs": { + "ext": { + "gdpr": 0 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/radianfusion/test-auction-radianfusion-response.json b/src/test/resources/org/prebid/server/it/openrtb2/radianfusion/test-auction-radianfusion-response.json new file mode 100644 index 00000000000..701a84c994b --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/radianfusion/test-auction-radianfusion-response.json @@ -0,0 +1,43 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "exp": 1500, + "price": 3.33, + "adm": "adm001", + "adid": "adid001", + "cid": "cid001", + "crid": "crid001", + "w": 300, + "h": 250, + "ext": { + "mediaType": "video", + "origbidcpm": 3.33, + "prebid": { + "type": "video", + "meta": { + "adaptercode": "radianfusion" + } + } + } + } + ], + "seat": "radianfusion", + "group": 0 + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "radianfusion": "{{ radianfusion.response_time_ms }}" + }, + "prebid": { + "auctiontimestamp": 0 + }, + "tmaxrequest": 5000 + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/radianfusion/test-radianfusion-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/radianfusion/test-radianfusion-bid-request.json new file mode 100644 index 00000000000..9342b97a4fa --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/radianfusion/test-radianfusion-bid-request.json @@ -0,0 +1,58 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "secure": 1, + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "tid": "${json-unit.any-string}", + "bidder": { + "partnerName": "someUniquePartnerName", + "seat": "someSeat", + "token": "someToken" + } + } + } + ], + "source": { + "tid": "${json-unit.any-string}" + }, + "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": "${json-unit.any-number}", + "cur": [ + "USD" + ], + "regs": { + "ext": { + "gdpr": 0 + } + }, + "ext": { + "prebid": { + "server": { + "externalurl": "http://localhost:8080", + "gvlid": 1, + "datacenter": "local", + "endpoint": "/openrtb2/auction" + } + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/radianfusion/test-radianfusion-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/radianfusion/test-radianfusion-bid-response.json new file mode 100644 index 00000000000..ecfbbed0ded --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/radianfusion/test-radianfusion-bid-response.json @@ -0,0 +1,23 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "price": 3.33, + "adid": "adid001", + "crid": "crid001", + "cid": "cid001", + "adm": "adm001", + "h": 250, + "w": 300, + "ext": { + "mediaType": "video" + } + } + ] + } + ] +} diff --git a/src/test/resources/org/prebid/server/it/test-application.properties b/src/test/resources/org/prebid/server/it/test-application.properties index 2a3ffe37083..07ea932405a 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -533,6 +533,8 @@ adapters.smarthub.aliases.addigi.enabled=true adapters.smarthub.aliases.addigi.endpoint=http://localhost:8090/addigi-exchange adapters.smarthub.aliases.artechnology.enabled=true adapters.smarthub.aliases.artechnology.endpoint=http://localhost:8090/artechnology-exchange?host={{Host}}&accountId={{AccountID}}&sourceId={{SourceId}} +adapters.smarthub.aliases.radianfusion.enabled=true +adapters.smarthub.aliases.radianfusion.endpoint=http://localhost:8090/radianfusion-exchange?host={{Host}}&accountId={{AccountID}}&sourceId={{SourceId}} adapters.smartyads.enabled=true adapters.smartyads.endpoint=http://localhost:8090/smartyads-exchange adapters.smilewanted.enabled=true From e1f5e96d6e4772bae3a6d308c0436c4f1d1cc632 Mon Sep 17 00:00:00 2001 From: przemkaczmarek <167743744+przemkaczmarek@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:32:38 +0100 Subject: [PATCH 25/38] Remove Mobupps alias (#4364) --- src/main/resources/bidder-config/adverxo.yaml | 14 ----- .../org/prebid/server/it/MobuppsTest.java | 33 ------------ .../mobupps/test-auction-mobupps-request.json | 34 ------------- .../test-auction-mobupps-response.json | 43 ---------------- .../mobupps/test-mobupps-bid-request.json | 51 ------------------- .../mobupps/test-mobupps-bid-response.json | 40 --------------- .../server/it/test-application.properties | 2 - 7 files changed, 217 deletions(-) delete mode 100644 src/test/java/org/prebid/server/it/MobuppsTest.java delete mode 100644 src/test/resources/org/prebid/server/it/openrtb2/mobupps/test-auction-mobupps-request.json delete mode 100644 src/test/resources/org/prebid/server/it/openrtb2/mobupps/test-auction-mobupps-response.json delete mode 100644 src/test/resources/org/prebid/server/it/openrtb2/mobupps/test-mobupps-bid-request.json delete mode 100644 src/test/resources/org/prebid/server/it/openrtb2/mobupps/test-mobupps-bid-response.json diff --git a/src/main/resources/bidder-config/adverxo.yaml b/src/main/resources/bidder-config/adverxo.yaml index d2fd18f505b..b363a70bc08 100644 --- a/src/main/resources/bidder-config/adverxo.yaml +++ b/src/main/resources/bidder-config/adverxo.yaml @@ -31,20 +31,6 @@ adapters: url: https://taetee.com/usync?type=image&gdpr={{gdpr}}&consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redirect={{redirect_url}} uid-macro: '$UID' support-cors: false - mobupps: - enabled: false - endpoint: https://mobupps.pbsadverxo.com/auction?id={{adUnitId}}&auth={{auth}} - usersync: - enabled: false - cookie-family-name: mobupps - iframe: - url: https://mobupps.pbsadverxo.com/usync?type=iframe&gdpr={{gdpr}}&consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redirect={{redirect_url}} - uid-macro: '$UID' - support-cors: false - redirect: - url: https://mobupps.pbsadverxo.com/usync?type=image&gdpr={{gdpr}}&consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redirect={{redirect_url}} - uid-macro: '$UID' - support-cors: false meta-info: maintainer-email: developer@adverxo.com app-media-types: diff --git a/src/test/java/org/prebid/server/it/MobuppsTest.java b/src/test/java/org/prebid/server/it/MobuppsTest.java deleted file mode 100644 index 9987de46469..00000000000 --- a/src/test/java/org/prebid/server/it/MobuppsTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.prebid.server.it; - -import io.restassured.response.Response; -import org.json.JSONException; -import org.junit.jupiter.api.Test; -import org.prebid.server.model.Endpoint; - -import java.io.IOException; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; -import static com.github.tomakehurst.wiremock.client.WireMock.post; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; -import static java.util.Collections.singletonList; - -public class MobuppsTest extends IntegrationTest { - - @Test - public void openrtb2AuctionShouldRespondWithBidsFromTheMobupps() throws IOException, JSONException { - // given - WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/mobupps-exchange")) - .withRequestBody(equalToJson(jsonFrom("openrtb2/mobupps/test-mobupps-bid-request.json"), true, true)) - .willReturn(aResponse().withBody(jsonFrom("openrtb2/mobupps/test-mobupps-bid-response.json")))); - - // when - final Response response = responseFor("openrtb2/mobupps/test-auction-mobupps-request.json", - Endpoint.openrtb2_auction); - - // then - assertJsonEquals("openrtb2/mobupps/test-auction-mobupps-response.json", response, - singletonList("mobupps")); - } -} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/mobupps/test-auction-mobupps-request.json b/src/test/resources/org/prebid/server/it/openrtb2/mobupps/test-auction-mobupps-request.json deleted file mode 100644 index 13f73b2a640..00000000000 --- a/src/test/resources/org/prebid/server/it/openrtb2/mobupps/test-auction-mobupps-request.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "id": "test-auction-request", - "imp": [ - { - "id": "imp1", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "mobupps": { - "adUnitId": 1, - "auth": "123456" - } - } - } - ], - "site": { - "page": "http://testpage.com" - }, - "device": { - "ua": "Mozilla/5.0" - }, - "tmax": 5000, - "regs": { - "ext": { - "gdpr": 0 - } - } -} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/mobupps/test-auction-mobupps-response.json b/src/test/resources/org/prebid/server/it/openrtb2/mobupps/test-auction-mobupps-response.json deleted file mode 100644 index 6019f0e1752..00000000000 --- a/src/test/resources/org/prebid/server/it/openrtb2/mobupps/test-auction-mobupps-response.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "id": "test-auction-request", - "seatbid": [ - { - "seat": "mobupps", - "group": 0, - "bid": [ - { - "id": "bid1", - "impid": "imp1", - "price": 1.23, - "adm": "", - "nurl": "https://example.com/win?price=1.23", - "crid": "creative1", - "w": 300, - "h": 250, - "exp": 300, - "mtype": 1, - "ext": { - "origbidcpm": 1.23, - "origbidcur": "USD", - "prebid": { - "type": "banner", - "meta": { - "adaptercode": "mobupps" - } - } - } - } - ] - } - ], - "cur": "USD", - "ext": { - "responsetimemillis": { - "mobupps": 0 - }, - "tmaxrequest": 5000, - "prebid": { - "auctiontimestamp": 0 - } - } -} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/mobupps/test-mobupps-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/mobupps/test-mobupps-bid-request.json deleted file mode 100644 index b1a0a5c0c8b..00000000000 --- a/src/test/resources/org/prebid/server/it/openrtb2/mobupps/test-mobupps-bid-request.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "id": "test-auction-request", - "imp": [ - { - "id": "imp1", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "secure": 1, - "ext": { - "tid": "${json-unit.any-string}", - "bidder": { - "adUnitId": 1, - "auth": "123456" - } - } - } - ], - "site": { - "domain": "testpage.com", - "page": "http://testpage.com", - "publisher": { - "domain": "testpage.com" - }, - "ext": { - "amp": 0 - } - }, - "device": { - "ua": "Mozilla/5.0", - "ip": "193.168.244.1" - }, - "at": 1, - "tmax": "${json-unit.any-number}", - "cur": [ - "USD" - ], - "source": { - "tid": "${json-unit.any-string}" - }, - "regs": { - "ext": { - "gdpr": 0 - } - } -} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/mobupps/test-mobupps-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/mobupps/test-mobupps-bid-response.json deleted file mode 100644 index 735fcd2ee33..00000000000 --- a/src/test/resources/org/prebid/server/it/openrtb2/mobupps/test-mobupps-bid-response.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "id": "test-auction-request", - "seatbid": [ - { - "seat": "mobupps", - "group": 0, - "bid": [ - { - "id": "bid1", - "impid": "imp1", - "price": 1.23, - "adm": "", - "nurl": "https://example.com/win?price=1.23", - "crid": "creative1", - "w": 300, - "h": 250, - "exp": 300, - "mtype": 1, - "ext": { - "origbidcpm": 1.23, - "origbidcur": "USD", - "prebid": { - "type": "banner" - } - } - } - ] - } - ], - "cur": "USD", - "ext": { - "responsetimemillis": { - "mobupps": 0 - }, - "tmaxrequest": 5000, - "prebid": { - "auctiontimestamp": 0 - } - } -} diff --git a/src/test/resources/org/prebid/server/it/test-application.properties b/src/test/resources/org/prebid/server/it/test-application.properties index 07ea932405a..01458b652a2 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -76,8 +76,6 @@ adapters.adverxo.aliases.adport.enabled=true adapters.adverxo.aliases.adport.endpoint=http://localhost:8090/adport-exchange adapters.adverxo.aliases.bidsmind.enabled=true adapters.adverxo.aliases.bidsmind.endpoint=http://localhost:8090/bidsmind-exchange -adapters.adverxo.aliases.mobupps.enabled=true -adapters.adverxo.aliases.mobupps.endpoint=http://localhost:8090/mobupps-exchange adapters.adview.enabled=true adapters.adview.endpoint=http://localhost:8090/adview-exchange?accountId={{AccountId}} adapters.adprime.enabled=true From 5ecd269707c878f0dfb70d6d73064076fb4fed4c Mon Sep 17 00:00:00 2001 From: przemkaczmarek <167743744+przemkaczmarek@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:33:42 +0100 Subject: [PATCH 26/38] Remove Vimayx alias from SmartHub adapter (#4363) --- .../resources/bidder-config/smarthub.yaml | 3 - .../java/org/prebid/server/it/VimayxTest.java | 36 ------------ .../vimayx/test-auction-vimayx-request.json | 25 -------- .../vimayx/test-auction-vimayx-response.json | 43 -------------- .../vimayx/test-vimayx-bid-request.json | 58 ------------------- .../vimayx/test-vimayx-bid-response.json | 23 -------- .../server/it/test-application.properties | 2 - 7 files changed, 190 deletions(-) delete mode 100644 src/test/java/org/prebid/server/it/VimayxTest.java delete mode 100644 src/test/resources/org/prebid/server/it/openrtb2/vimayx/test-auction-vimayx-request.json delete mode 100644 src/test/resources/org/prebid/server/it/openrtb2/vimayx/test-auction-vimayx-response.json delete mode 100644 src/test/resources/org/prebid/server/it/openrtb2/vimayx/test-vimayx-bid-request.json delete mode 100644 src/test/resources/org/prebid/server/it/openrtb2/vimayx/test-vimayx-bid-response.json diff --git a/src/main/resources/bidder-config/smarthub.yaml b/src/main/resources/bidder-config/smarthub.yaml index d9441f1c0f3..e3d370266b5 100644 --- a/src/main/resources/bidder-config/smarthub.yaml +++ b/src/main/resources/bidder-config/smarthub.yaml @@ -11,9 +11,6 @@ adapters: tredio: enabled: false endpoint: https://tredio-prebid.attekmi.com/pbserver/?seat={{AccountID}}&token={{SourceId}} - vimayx: - enabled: false - endpoint: https://vimayx-prebid.attekmi.com/pbserver/?seat={{AccountID}}&token={{SourceId}} felixads: enabled: false endpoint: https://felixads-prebid.attekmi.com/pbserver/?seat={{AccountID}}&token={{SourceId}} diff --git a/src/test/java/org/prebid/server/it/VimayxTest.java b/src/test/java/org/prebid/server/it/VimayxTest.java deleted file mode 100644 index ac7750065d0..00000000000 --- a/src/test/java/org/prebid/server/it/VimayxTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.prebid.server.it; - -import io.restassured.response.Response; -import org.json.JSONException; -import org.junit.jupiter.api.Test; -import org.prebid.server.model.Endpoint; - -import java.io.IOException; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; -import static com.github.tomakehurst.wiremock.client.WireMock.post; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; -import static java.util.Collections.singletonList; - -public class VimayxTest extends IntegrationTest { - - @Test - public void openrtb2AuctionShouldRespondWithBidsFromVimayx() throws IOException, JSONException { - // given - WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/vimayx-exchange")) - .withQueryParam("host", equalTo("someUniquePartnerName")) - .withQueryParam("accountId", equalTo("someSeat")) - .withQueryParam("sourceId", equalTo("someToken")) - .withRequestBody(equalToJson(jsonFrom("openrtb2/vimayx/test-vimayx-bid-request.json"))) - .willReturn(aResponse().withBody(jsonFrom("openrtb2/vimayx/test-vimayx-bid-response.json")))); - - // when - final Response response = responseFor("openrtb2/vimayx/test-auction-vimayx-request.json", - Endpoint.openrtb2_auction); - - // then - assertJsonEquals("openrtb2/vimayx/test-auction-vimayx-response.json", response, singletonList("vimayx")); - } -} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/vimayx/test-auction-vimayx-request.json b/src/test/resources/org/prebid/server/it/openrtb2/vimayx/test-auction-vimayx-request.json deleted file mode 100644 index 37f89d39672..00000000000 --- a/src/test/resources/org/prebid/server/it/openrtb2/vimayx/test-auction-vimayx-request.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "id": "request_id", - "imp": [ - { - "id": "imp_id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "vimayx": { - "partnerName": "someUniquePartnerName", - "seat": "someSeat", - "token": "someToken" - } - } - } - ], - "tmax": 5000, - "regs": { - "ext": { - "gdpr": 0 - } - } -} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/vimayx/test-auction-vimayx-response.json b/src/test/resources/org/prebid/server/it/openrtb2/vimayx/test-auction-vimayx-response.json deleted file mode 100644 index 218b16cda60..00000000000 --- a/src/test/resources/org/prebid/server/it/openrtb2/vimayx/test-auction-vimayx-response.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "id": "request_id", - "seatbid": [ - { - "bid": [ - { - "id": "bid_id", - "impid": "imp_id", - "exp": 1500, - "price": 3.33, - "adm": "adm001", - "adid": "adid001", - "cid": "cid001", - "crid": "crid001", - "w": 300, - "h": 250, - "ext": { - "mediaType": "video", - "origbidcpm": 3.33, - "prebid": { - "type": "video", - "meta": { - "adaptercode": "vimayx" - } - } - } - } - ], - "seat": "vimayx", - "group": 0 - } - ], - "cur": "USD", - "ext": { - "responsetimemillis": { - "vimayx": "{{ vimayx.response_time_ms }}" - }, - "prebid": { - "auctiontimestamp": 0 - }, - "tmaxrequest": 5000 - } -} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/vimayx/test-vimayx-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/vimayx/test-vimayx-bid-request.json deleted file mode 100644 index 9342b97a4fa..00000000000 --- a/src/test/resources/org/prebid/server/it/openrtb2/vimayx/test-vimayx-bid-request.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "id": "request_id", - "imp": [ - { - "id": "imp_id", - "secure": 1, - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "tid": "${json-unit.any-string}", - "bidder": { - "partnerName": "someUniquePartnerName", - "seat": "someSeat", - "token": "someToken" - } - } - } - ], - "source": { - "tid": "${json-unit.any-string}" - }, - "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": "${json-unit.any-number}", - "cur": [ - "USD" - ], - "regs": { - "ext": { - "gdpr": 0 - } - }, - "ext": { - "prebid": { - "server": { - "externalurl": "http://localhost:8080", - "gvlid": 1, - "datacenter": "local", - "endpoint": "/openrtb2/auction" - } - } - } -} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/vimayx/test-vimayx-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/vimayx/test-vimayx-bid-response.json deleted file mode 100644 index ecfbbed0ded..00000000000 --- a/src/test/resources/org/prebid/server/it/openrtb2/vimayx/test-vimayx-bid-response.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id": "request_id", - "seatbid": [ - { - "bid": [ - { - "id": "bid_id", - "impid": "imp_id", - "price": 3.33, - "adid": "adid001", - "crid": "crid001", - "cid": "cid001", - "adm": "adm001", - "h": 250, - "w": 300, - "ext": { - "mediaType": "video" - } - } - ] - } - ] -} diff --git a/src/test/resources/org/prebid/server/it/test-application.properties b/src/test/resources/org/prebid/server/it/test-application.properties index 01458b652a2..41dc9fdb8ab 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -519,8 +519,6 @@ adapters.smarthub.aliases.jdpmedia.enabled=true adapters.smarthub.aliases.jdpmedia.endpoint=http://localhost:8090/jdpmedia-exchange?host={{Host}}&accountId={{AccountID}}&sourceId={{SourceId}} adapters.smarthub.aliases.tredio.enabled=true adapters.smarthub.aliases.tredio.endpoint=http://localhost:8090/tredio-exchange?host={{Host}}&accountId={{AccountID}}&sourceId={{SourceId}} -adapters.smarthub.aliases.vimayx.enabled=true -adapters.smarthub.aliases.vimayx.endpoint=http://localhost:8090/vimayx-exchange?host={{Host}}&accountId={{AccountID}}&sourceId={{SourceId}} adapters.smarthub.aliases.felixads.enabled=true adapters.smarthub.aliases.felixads.endpoint=http://localhost:8090/felixads-exchange?host={{Host}}&accountId={{AccountID}}&sourceId={{SourceId}} adapters.smarthub.aliases.jambojar.enabled=true From 3934e336fb820f39397c970cd59c9ee68f7c3e1c Mon Sep 17 00:00:00 2001 From: przemkaczmarek <167743744+przemkaczmarek@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:34:06 +0100 Subject: [PATCH 27/38] Migrate ProgX alias from Vidazoo to Teqblaze adapter (#4356) --- src/main/resources/bidder-config/teqblaze.yaml | 17 +++++++++++++++++ src/main/resources/bidder-config/vidazoo.yaml | 13 ------------- .../java/org/prebid/server/it/ProgxTest.java | 2 +- .../progx/test-auction-progx-request.json | 5 ++--- .../progx/test-auction-progx-response.json | 10 ++++------ .../openrtb2/progx/test-progx-bid-request.json | 16 +++++++++------- .../progx/test-progx-bid-response.json | 18 ++++++++++-------- .../server/it/test-application.properties | 4 ++-- 8 files changed, 45 insertions(+), 40 deletions(-) diff --git a/src/main/resources/bidder-config/teqblaze.yaml b/src/main/resources/bidder-config/teqblaze.yaml index 5e1006433ae..9938c49f107 100644 --- a/src/main/resources/bidder-config/teqblaze.yaml +++ b/src/main/resources/bidder-config/teqblaze.yaml @@ -43,6 +43,23 @@ adapters: mata-info: maintainer-email: support@gravite.net vendor-id: 377 + progx: + enabled: false + endpoint: https://us-east.progrtb.com/pserver + meta-info: + maintainer-email: pxteam@programmaticx.ai + vendor-id: 1344 + usersync: + enabled: true + cookie-family-name: progx + iframe: + url: https://sync.progrtb.com/pbserverIframe?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&ccpa={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&pbserverUrl={{.RedirectURL}} + support-cors: false + uid-macro: '[UID]' + redirect: + url: https://sync.progrtb.com/pbserver?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&ccpa={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&redir={{redirect_url}} + support-cors: false + uid-macro: '[UID]' meta-info: maintainer-email: github@teqblaze.com app-media-types: diff --git a/src/main/resources/bidder-config/vidazoo.yaml b/src/main/resources/bidder-config/vidazoo.yaml index 3e1768fb0cb..adb9b9758a5 100644 --- a/src/main/resources/bidder-config/vidazoo.yaml +++ b/src/main/resources/bidder-config/vidazoo.yaml @@ -2,19 +2,6 @@ adapters: vidazoo: endpoint: https://prebidsrvr.cootlogix.com/openrtb/ aliases: - progx: - enabled: false - endpoint: https://exchange.programmaticx.ai/openrtb/ - meta-info: - maintainer-email: pxteam@programmaticx.ai - vendor-id: 1344 - usersync: - enabled: true - cookie-family-name: progx - iframe: - url: https://sync.programmaticx.ai/api/user/html/685297194d85991a5e6e36dd?pbs=true&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redirect={{redirect_url}}&gpp={{gpp}}&gpp_sid={{gpp_sid}} - support-cors: false - uid-macro: '${userId}' omnidex: endpoint: https://exchange.omni-dex.io/openrtb/ usersync: diff --git a/src/test/java/org/prebid/server/it/ProgxTest.java b/src/test/java/org/prebid/server/it/ProgxTest.java index 6f77d217a86..c84a146ba7d 100644 --- a/src/test/java/org/prebid/server/it/ProgxTest.java +++ b/src/test/java/org/prebid/server/it/ProgxTest.java @@ -18,7 +18,7 @@ public class ProgxTest extends IntegrationTest { @Test public void openrtb2AuctionShouldRespondWithBidsFromProgx() throws IOException, JSONException { // given - WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/progx-exchange/connectionId")) + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/progx-exchange/")) .withRequestBody(equalToJson(jsonFrom("openrtb2/progx/test-progx-bid-request.json"))) .willReturn(aResponse().withBody(jsonFrom("openrtb2/progx/test-progx-bid-response.json")))); diff --git a/src/test/resources/org/prebid/server/it/openrtb2/progx/test-auction-progx-request.json b/src/test/resources/org/prebid/server/it/openrtb2/progx/test-auction-progx-request.json index 3c416a84304..c1994dd61a0 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/progx/test-auction-progx-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/progx/test-auction-progx-request.json @@ -3,14 +3,13 @@ "imp": [ { "id": "imp_id", - "secure": 1, "banner": { - "w": 320, + "w": 300, "h": 250 }, "ext": { "progx": { - "cId": "connectionId" + "placementId": "testPlacementId" } } } diff --git a/src/test/resources/org/prebid/server/it/openrtb2/progx/test-auction-progx-response.json b/src/test/resources/org/prebid/server/it/openrtb2/progx/test-auction-progx-response.json index b7437013277..59f6dffcf46 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/progx/test-auction-progx-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/progx/test-auction-progx-response.json @@ -7,19 +7,17 @@ "id": "bid_id", "impid": "imp_id", "exp": 300, - "price": 0.01, - "adid": "2068416", - "cid": "8048", - "crid": "24080", + "price": 3.33, + "crid": "creativeId", "mtype": 1, "ext": { + "origbidcpm": 3.33, "prebid": { "type": "banner", "meta": { "adaptercode": "progx" } - }, - "origbidcpm": 0.01 + } } } ], diff --git a/src/test/resources/org/prebid/server/it/openrtb2/progx/test-progx-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/progx/test-progx-bid-request.json index eb67f5687e5..dcf904ba566 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/progx/test-progx-bid-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/progx/test-progx-bid-request.json @@ -3,22 +3,19 @@ "imp": [ { "id": "imp_id", - "secure": 1, "banner": { - "w": 320, + "w": 300, "h": 250 }, + "secure": 1, "ext": { - "tid": "${json-unit.any-string}", "bidder": { - "cId": "connectionId" + "type": "publisher", + "placementId": "testPlacementId" } } } ], - "source": { - "tid": "${json-unit.any-string}" - }, "site": { "domain": "www.example.com", "page": "http://www.example.com", @@ -38,8 +35,13 @@ "cur": [ "USD" ], + "source": { + "tid": "${json-unit.any-string}" + }, "regs": { + "ext": { "gdpr": 0 + } }, "ext": { "prebid": { diff --git a/src/test/resources/org/prebid/server/it/openrtb2/progx/test-progx-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/progx/test-progx-bid-response.json index 47d4f8718ea..180173549d8 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/progx/test-progx-bid-response.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/progx/test-progx-bid-response.json @@ -1,19 +1,21 @@ { - "id": "tid", + "id": "request_id", "seatbid": [ { "bid": [ { - "crid": "24080", - "adid": "2068416", - "price": 0.01, "id": "bid_id", "impid": "imp_id", - "cid": "8048", - "mtype": 1 + "price": 3.33, + "crid": "creativeId", + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } } - ], - "type": "banner" + ] } ] } diff --git a/src/test/resources/org/prebid/server/it/test-application.properties b/src/test/resources/org/prebid/server/it/test-application.properties index 41dc9fdb8ab..c6243333b71 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -583,6 +583,8 @@ adapters.teqblaze.aliases.appStockSSP.enabled=true adapters.teqblaze.aliases.appStockSSP.endpoint=http://localhost:8090/appstockssp-exchange adapters.teqblaze.aliases.gravite.enabled=true adapters.teqblaze.aliases.gravite.endpoint=http://localhost:8090/gravite-exchange +adapters.teqblaze.aliases.progx.enabled=true +adapters.teqblaze.aliases.progx.endpoint=http://localhost:8090/progx-exchange/ adapters.theadx.enabled=true adapters.theadx.endpoint=http://localhost:8090/theadx-exchange adapters.tradplus.enabled=true @@ -635,8 +637,6 @@ adapters.xeworks.aliases.adipolo.enabled=true adapters.xeworks.aliases.adipolo.endpoint=http://localhost:8090/adipolo-exchange adapters.vidazoo.enabled=true adapters.vidazoo.endpoint=http://localhost:8090/vidazoo-exchange/ -adapters.vidazoo.aliases.progx.enabled=true -adapters.vidazoo.aliases.progx.endpoint=http://localhost:8090/progx-exchange/ adapters.vidazoo.aliases.omnidex.enabled=true adapters.vidazoo.aliases.omnidex.endpoint=http://localhost:8090/omnidex-exchange/ adapters.vidazoo.aliases.tagoras.enabled=true From 6f8483d984d0822bcae05604ec284ceab2296c20 Mon Sep 17 00:00:00 2001 From: przemkaczmarek <167743744+przemkaczmarek@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:34:18 +0100 Subject: [PATCH 28/38] AdOcean: Remove adapter (#4355) --- .../server/bidder/adocean/AdoceanBidder.java | 358 ----------- .../adocean/model/AdoceanResponseAdUnit.java | 32 - .../ext/request/adocean/ExtImpAdocean.java | 20 - .../config/bidder/AdoceanConfiguration.java | 41 -- src/main/resources/bidder-config/adocean.yaml | 11 - .../static/bidder-params/adocean.json | 44 -- .../bidder/adocean/AdoceanBidderTest.java | 572 ------------------ .../bidder/smarthub/SmarthubBidderTest.java | 3 +- .../org/prebid/server/it/AdoceanTest.java | 30 - .../adocean/test-adocean-bid-response-1.json | 14 - .../adocean/test-auction-adocean-request.json | 29 - .../test-auction-adocean-response.json | 41 -- .../server/it/test-application.properties | 2 - 13 files changed, 1 insertion(+), 1196 deletions(-) delete mode 100644 src/main/java/org/prebid/server/bidder/adocean/AdoceanBidder.java delete mode 100644 src/main/java/org/prebid/server/bidder/adocean/model/AdoceanResponseAdUnit.java delete mode 100644 src/main/java/org/prebid/server/proto/openrtb/ext/request/adocean/ExtImpAdocean.java delete mode 100644 src/main/java/org/prebid/server/spring/config/bidder/AdoceanConfiguration.java delete mode 100644 src/main/resources/bidder-config/adocean.yaml delete mode 100644 src/main/resources/static/bidder-params/adocean.json delete mode 100644 src/test/java/org/prebid/server/bidder/adocean/AdoceanBidderTest.java delete mode 100644 src/test/java/org/prebid/server/it/AdoceanTest.java delete mode 100644 src/test/resources/org/prebid/server/it/openrtb2/adocean/test-adocean-bid-response-1.json delete mode 100644 src/test/resources/org/prebid/server/it/openrtb2/adocean/test-auction-adocean-request.json delete mode 100644 src/test/resources/org/prebid/server/it/openrtb2/adocean/test-auction-adocean-response.json diff --git a/src/main/java/org/prebid/server/bidder/adocean/AdoceanBidder.java b/src/main/java/org/prebid/server/bidder/adocean/AdoceanBidder.java deleted file mode 100644 index 63ad320ca4e..00000000000 --- a/src/main/java/org/prebid/server/bidder/adocean/AdoceanBidder.java +++ /dev/null @@ -1,358 +0,0 @@ -package org.prebid.server.bidder.adocean; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.iab.openrtb.request.App; -import com.iab.openrtb.request.Banner; -import com.iab.openrtb.request.BidRequest; -import com.iab.openrtb.request.Device; -import com.iab.openrtb.request.Format; -import com.iab.openrtb.request.Imp; -import com.iab.openrtb.request.Site; -import com.iab.openrtb.request.User; -import com.iab.openrtb.response.Bid; -import io.vertx.core.MultiMap; -import io.vertx.core.http.HttpMethod; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.collections4.MapUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.math.NumberUtils; -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.client.utils.URLEncodedUtils; -import org.apache.http.message.BasicNameValuePair; -import org.prebid.server.bidder.Bidder; -import org.prebid.server.bidder.adocean.model.AdoceanResponseAdUnit; -import org.prebid.server.bidder.model.BidderBid; -import org.prebid.server.bidder.model.BidderCall; -import org.prebid.server.bidder.model.BidderError; -import org.prebid.server.bidder.model.HttpRequest; -import org.prebid.server.bidder.model.Result; -import org.prebid.server.exception.PreBidException; -import org.prebid.server.json.JacksonMapper; -import org.prebid.server.proto.openrtb.ext.ExtPrebid; -import org.prebid.server.proto.openrtb.ext.request.ExtUser; -import org.prebid.server.proto.openrtb.ext.request.adocean.ExtImpAdocean; -import org.prebid.server.proto.openrtb.ext.response.BidType; -import org.prebid.server.util.HttpUtil; - -import java.io.IOException; -import java.math.BigDecimal; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.TreeSet; -import java.util.stream.Collectors; - -public class AdoceanBidder implements Bidder { - - private static final TypeReference> ADOCEAN_EXT_TYPE_REFERENCE = - new TypeReference<>() { - }; - private static final String VERSION = "1.3.0"; - private static final int MAX_URI_LENGTH = 8000; - private static final String MEASUREMENT_CODE_TEMPLATE = """ - """; - - private final String endpointUrl; - private final JacksonMapper mapper; - - public AdoceanBidder(String endpointUrl, JacksonMapper mapper) { - this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); - this.mapper = Objects.requireNonNull(mapper); - } - - @Override - public Result>> makeHttpRequests(BidRequest request) { - final List errors = new ArrayList<>(); - final User user = request.getUser(); - final ExtUser extUser = user != null ? user.getExt() : null; - final String consentString = extUser != null ? extUser.getConsent() : ""; - - final List> httpRequests = new ArrayList<>(); - for (Imp imp : request.getImp()) { - try { - final ExtImpAdocean extImpAdocean = parseImpExt(imp); - validateImpExt(extImpAdocean); - - final Map slaveSizes = new HashMap<>(); - slaveSizes.put(extImpAdocean.getSlaveId(), getImpSizes(imp)); - if (addRequestAndCheckIfDuplicates(httpRequests, extImpAdocean, imp.getId(), slaveSizes, - request.getTest())) { - continue; - } - httpRequests.add(createSingleRequest(request, imp, extImpAdocean, consentString, slaveSizes)); - } catch (PreBidException e) { - errors.add(BidderError.badInput(e.getMessage())); - } - } - - return Result.of(httpRequests, errors); - } - - private ExtImpAdocean parseImpExt(Imp imp) { - try { - return mapper.mapper().convertValue(imp.getExt(), ADOCEAN_EXT_TYPE_REFERENCE).getBidder(); - } catch (IllegalArgumentException e) { - throw new PreBidException( - "Error parsing adOceanExt parameters, in imp with id : " + imp.getId()); - } - } - - private static void validateImpExt(ExtImpAdocean impExt) { - if (StringUtils.isEmpty(impExt.getEmitterPrefix())) { - throw new PreBidException("No emitterPrefix param"); - } - } - - private boolean addRequestAndCheckIfDuplicates(List> httpRequests, ExtImpAdocean extImpAdocean, - String impid, Map slaveSizes, Integer test) { - for (HttpRequest request : httpRequests) { - try { - final URIBuilder uriBuilder = new URIBuilder(request.getUri()); - final List queryParams = uriBuilder.getQueryParams(); - - final String masterId = queryParams.stream() - .filter(param -> "id".equals(param.getName())) - .findFirst() - .map(NameValuePair::getValue) - .orElse(null); - - if (masterId != null && masterId.equals(extImpAdocean.getMasterId())) { - final boolean isExistingSlaveId = queryParams.stream() - .filter(param -> "aid".equals(param.getName())) - .map(param -> param.getValue().split(":")[0]) - .anyMatch(slaveId -> slaveId.equals(extImpAdocean.getSlaveId())); - if (isExistingSlaveId) { - continue; - } - - queryParams.add(new BasicNameValuePair("aid", extImpAdocean.getSlaveId() + ":" + impid)); - final List sizeValues = setSlaveSizesParam(slaveSizes, Objects.equals(test, 1)); - if (CollectionUtils.isNotEmpty(sizeValues)) { - queryParams.add(new BasicNameValuePair("aosspsizes", String.join("-", sizeValues))); - } - uriBuilder.setParameters(queryParams); - - final String url = uriBuilder.toString(); - if (url.length() < MAX_URI_LENGTH) { - final HttpRequest updatedRequest = HttpRequest.builder() - .method(HttpMethod.GET) - .uri(url) - .headers(request.getHeaders()) - .build(); - httpRequests.remove(request); - httpRequests.add(updatedRequest); - return true; - } - } - } catch (URISyntaxException e) { - throw new PreBidException(e.getMessage()); - } - } - return false; - } - - private String getImpSizes(Imp imp) { - final Banner banner = imp.getBanner(); - if (banner == null) { - return ""; - } - - final List format = banner.getFormat(); - if (CollectionUtils.isNotEmpty(format)) { - final List sizes = new ArrayList<>(); - format.forEach(singleFormat -> sizes.add( - "%sx%s".formatted(getIntOrElseZero(singleFormat.getW()), getIntOrElseZero(singleFormat.getH())))); - return String.join("_", sizes); - } - - final Integer w = banner.getW(); - final Integer h = banner.getH(); - if (w != null && h != null) { - return "%sx%s".formatted(w, h); - } - - return StringUtils.EMPTY; - } - - private int getIntOrElseZero(Integer number) { - return number != null ? number : 0; - } - - private HttpRequest createSingleRequest(BidRequest request, Imp imp, ExtImpAdocean extImpAdocean, - String consentString, Map slaveSizes) { - - return HttpRequest.builder() - .method(HttpMethod.GET) - .uri(buildUrl(imp.getId(), extImpAdocean, consentString, request, slaveSizes)) - .headers(getHeaders(request)) - .build(); - } - - private String buildUrl(String impId, ExtImpAdocean extImpAdocean, String consentString, BidRequest bidRequest, - Map slaveSizes) { - - final Integer test = bidRequest.getTest(); - final String resolvedUrl = resolveEndpointUrl(extImpAdocean, test); - - final URIBuilder uriBuilder; - try { - uriBuilder = new URIBuilder(resolvedUrl); - } catch (URISyntaxException e) { - throw new PreBidException("Invalid url: %s, error: %s".formatted(resolvedUrl, e.getMessage())); - } - - uriBuilder - .addParameter("pbsrv_v", VERSION) - .addParameter("id", extImpAdocean.getMasterId()) - .addParameter("nc", "1") - .addParameter("nosecure", "1") - .addParameter("aid", extImpAdocean.getSlaveId() + ":" + impId); - - if (StringUtils.isNotEmpty(consentString)) { - uriBuilder.addParameter("gdpr_consent", consentString); - uriBuilder.addParameter("gdpr", "1"); - } - - final User user = bidRequest.getUser(); - if (user != null && StringUtils.isNotEmpty(user.getBuyeruid())) { - uriBuilder.addParameter("hcuserid", user.getBuyeruid()); - } - - final App app = bidRequest.getApp(); - if (app != null) { - uriBuilder.addParameter("app", "1"); - uriBuilder.addParameter("appname", app.getName()); - uriBuilder.addParameter("appbundle", app.getBundle()); - uriBuilder.addParameter("appdomain", app.getDomain()); - } - - final Device device = bidRequest.getDevice(); - if (device != null) { - if (StringUtils.isNotEmpty(device.getIfa())) { - uriBuilder.addParameter("ifa", device.getIfa()); - } else { - uriBuilder.addParameter("dpidmd5", device.getDpidmd5()); - } - - uriBuilder.addParameter("devos", device.getOs()); - uriBuilder.addParameter("devosv", device.getOsv()); - uriBuilder.addParameter("devmodel", device.getModel()); - uriBuilder.addParameter("devmake", device.getMake()); - } - - final List sizeValues = setSlaveSizesParam(slaveSizes, Objects.equals(test, 1)); - - if (CollectionUtils.isNotEmpty(sizeValues)) { - uriBuilder.addParameter("aosspsizes", String.join("-", sizeValues)); - } - - return uriBuilder.toString(); - } - - private String resolveEndpointUrl(ExtImpAdocean extImpAdocean, Integer test) { - final String url = endpointUrl.replace("{{Host}}", extImpAdocean.getEmitterPrefix()); - final int randomizedPart = Objects.equals(test, 1) ? 10000000 : 10000000 + (int) (Math.random() * 89999999); - return "%s/_%s/ad.json".formatted(url, randomizedPart); - } - - private List setSlaveSizesParam(Map slaveSizes, boolean orderByKey) { - final Set slaveIDs = orderByKey ? new TreeSet<>(slaveSizes.keySet()) : slaveSizes.keySet(); - - return slaveIDs.stream() - .filter(slaveId -> StringUtils.isNotEmpty(slaveSizes.get(slaveId))) - .map(rawSlaveID -> "%s~%s".formatted( - rawSlaveID.replaceFirst("adocean", ""), - slaveSizes.get(rawSlaveID))) - .toList(); - } - - private static MultiMap getHeaders(BidRequest request) { - final MultiMap headers = HttpUtil.headers(); - - final Device device = request.getDevice(); - if (device != null) { - HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.USER_AGENT_HEADER, device.getUa()); - HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIpv6()); - HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIp()); - } - - final Site site = request.getSite(); - if (site != null) { - HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.REFERER_HEADER, site.getPage()); - } - - return headers; - } - - @Override - public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { - final List params; - try { - params = URLEncodedUtils.parse(new URI(httpCall.getRequest().getUri()), StandardCharsets.UTF_8); - } catch (URISyntaxException e) { - return Result.withError(BidderError.badInput(e.getMessage())); - } - - final Map auctionIds = params != null ? params.stream() - .filter(param -> "aid".equals(param.getName())) - .map(param -> param.getValue().split(":")) - .collect(Collectors.toMap(name -> name[0], value -> value[1])) : null; - - final List adoceanResponses; - try { - adoceanResponses = getAdoceanResponseAdUnitList(httpCall.getResponse().getBody()); - } catch (PreBidException e) { - return Result.withError(BidderError - .badServerResponse("Failed to decode: No content to map due to end-of-input")); - } - - final List bidderBids = adoceanResponses.stream() - .filter(adoceanResponse -> !"true".equals(adoceanResponse.getError())) - .filter(adoceanResponse -> - StringUtils.isNotBlank(MapUtils.getString(auctionIds, adoceanResponse.getId()))) - .map(adoceanResponse -> BidderBid.of(createBid(auctionIds, adoceanResponse), BidType.banner, - adoceanResponse.getCurrency())) - .toList(); - - return Result.withValues(bidderBids); - } - - private static Bid createBid(Map auctionIds, AdoceanResponseAdUnit adoceanResponse) { - final String adm = MEASUREMENT_CODE_TEMPLATE - .formatted(adoceanResponse.getWinUrl(), adoceanResponse.getStatsUrl()) - + HttpUtil.decodeUrl(adoceanResponse.getCode()); - final String bidPrice = adoceanResponse.getPrice(); - - return Bid.builder() - .id(adoceanResponse.getId()) - .impid(auctionIds.get(adoceanResponse.getId())) - .adm(adm) - .price(NumberUtils.isParsable(bidPrice) ? new BigDecimal(bidPrice) : BigDecimal.ZERO) - .w(NumberUtils.toInt(adoceanResponse.getWidth(), 0)) - .h(NumberUtils.toInt(adoceanResponse.getHeight(), 0)) - .crid(adoceanResponse.getCrid()) - .build(); - } - - private List getAdoceanResponseAdUnitList(String responseBody) { - try { - return mapper.mapper().readValue( - responseBody, - mapper.mapper().getTypeFactory().constructCollectionType(List.class, AdoceanResponseAdUnit.class)); - } catch (IOException e) { - throw new PreBidException(e.getMessage()); - } - } -} diff --git a/src/main/java/org/prebid/server/bidder/adocean/model/AdoceanResponseAdUnit.java b/src/main/java/org/prebid/server/bidder/adocean/model/AdoceanResponseAdUnit.java deleted file mode 100644 index b2bd0142cee..00000000000 --- a/src/main/java/org/prebid/server/bidder/adocean/model/AdoceanResponseAdUnit.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.prebid.server.bidder.adocean.model; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Builder; -import lombok.Value; - -@Builder -@Value -public class AdoceanResponseAdUnit { - - String id; - - String crid; - - String currency; - - String price; - - String width; - - String height; - - String code; - - @JsonProperty("winurl") - String winUrl; - - @JsonProperty("statsUrl") - String statsUrl; - - String error; -} diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/adocean/ExtImpAdocean.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/adocean/ExtImpAdocean.java deleted file mode 100644 index aa364d222ae..00000000000 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/adocean/ExtImpAdocean.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.prebid.server.proto.openrtb.ext.request.adocean; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Value; - -/** - * Defines the contract for bidrequest.imp[i].ext.adocean - */ -@Value(staticConstructor = "of") -public class ExtImpAdocean { - - @JsonProperty("emitterPrefix") - String emitterPrefix; - - @JsonProperty("masterId") - String masterId; - - @JsonProperty("slaveId") - String slaveId; -} diff --git a/src/main/java/org/prebid/server/spring/config/bidder/AdoceanConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/AdoceanConfiguration.java deleted file mode 100644 index d16984e3c5e..00000000000 --- a/src/main/java/org/prebid/server/spring/config/bidder/AdoceanConfiguration.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.prebid.server.spring.config.bidder; - -import org.prebid.server.bidder.BidderDeps; -import org.prebid.server.bidder.adocean.AdoceanBidder; -import org.prebid.server.json.JacksonMapper; -import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties; -import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler; -import org.prebid.server.spring.config.bidder.util.UsersyncerCreator; -import org.prebid.server.spring.env.YamlPropertySourceFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.PropertySource; - -import jakarta.validation.constraints.NotBlank; - -@Configuration -@PropertySource(value = "classpath:/bidder-config/adocean.yaml", factory = YamlPropertySourceFactory.class) -public class AdoceanConfiguration { - - private static final String BIDDER_NAME = "adocean"; - - @Bean("adoceanConfigurationProperties") - @ConfigurationProperties("adapters.adocean") - BidderConfigurationProperties configurationProperties() { - return new BidderConfigurationProperties(); - } - - @Bean - BidderDeps adoceanBidderDeps(BidderConfigurationProperties adoceanConfigurationProperties, - @NotBlank @Value("${external-url}") String externalUrl, - JacksonMapper mapper) { - - return BidderDepsAssembler.forBidder(BIDDER_NAME) - .withConfig(adoceanConfigurationProperties) - .usersyncerCreator(UsersyncerCreator.create(externalUrl)) - .bidderCreator(config -> new AdoceanBidder(config.getEndpoint(), mapper)) - .assemble(); - } -} diff --git a/src/main/resources/bidder-config/adocean.yaml b/src/main/resources/bidder-config/adocean.yaml deleted file mode 100644 index f659947c985..00000000000 --- a/src/main/resources/bidder-config/adocean.yaml +++ /dev/null @@ -1,11 +0,0 @@ -adapters: - adocean: - endpoint: https://{{Host}}.adocean.pl - meta-info: - maintainer-email: aoteam@gemius.com - app-media-types: - - banner - site-media-types: - - banner - supported-vendors: - vendor-id: 328 diff --git a/src/main/resources/static/bidder-params/adocean.json b/src/main/resources/static/bidder-params/adocean.json deleted file mode 100644 index 5c62c410fb1..00000000000 --- a/src/main/resources/static/bidder-params/adocean.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "AdOcean Adapter Params", - "description": "A schema which validates params accepted by the AdOcean adapter", - "type": "object", - "properties": { - "emiter": { - "type": "string", - "description": "Deprecated, use emitterPrefix instead. AdOcean emiter", - "pattern": ".+" - }, - "emitterPrefix": { - "type": "string", - "description": "AdOcean emitter prefix", - "pattern": "^[\\w\\-]+$" - }, - "masterId": { - "type": "string", - "description": "Master's id", - "pattern": "^[\\w.]+$" - }, - "slaveId": { - "type": "string", - "description": "Slave's id", - "pattern": "^adocean[\\w.]+$" - } - }, - "oneOf": [ - { - "required": [ - "emiter", - "masterId", - "slaveId" - ] - }, - { - "required": [ - "emitterPrefix", - "masterId", - "slaveId" - ] - } - ] -} diff --git a/src/test/java/org/prebid/server/bidder/adocean/AdoceanBidderTest.java b/src/test/java/org/prebid/server/bidder/adocean/AdoceanBidderTest.java deleted file mode 100644 index 9657dd88767..00000000000 --- a/src/test/java/org/prebid/server/bidder/adocean/AdoceanBidderTest.java +++ /dev/null @@ -1,572 +0,0 @@ -package org.prebid.server.bidder.adocean; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.iab.openrtb.request.App; -import com.iab.openrtb.request.Banner; -import com.iab.openrtb.request.BidRequest; -import com.iab.openrtb.request.Device; -import com.iab.openrtb.request.Format; -import com.iab.openrtb.request.Imp; -import com.iab.openrtb.request.Site; -import com.iab.openrtb.request.User; -import com.iab.openrtb.response.Bid; -import io.netty.handler.codec.http.HttpHeaderValues; -import org.junit.jupiter.api.Test; -import org.prebid.server.VertxTest; -import org.prebid.server.bidder.adocean.model.AdoceanResponseAdUnit; -import org.prebid.server.bidder.model.BidderBid; -import org.prebid.server.bidder.model.BidderCall; -import org.prebid.server.bidder.model.BidderError; -import org.prebid.server.bidder.model.HttpRequest; -import org.prebid.server.bidder.model.HttpResponse; -import org.prebid.server.bidder.model.Result; -import org.prebid.server.proto.openrtb.ext.ExtPrebid; -import org.prebid.server.proto.openrtb.ext.request.ExtUser; -import org.prebid.server.proto.openrtb.ext.request.adocean.ExtImpAdocean; -import org.prebid.server.proto.openrtb.ext.response.BidType; -import org.prebid.server.util.HttpUtil; - -import java.math.BigDecimal; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.function.Function; - -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static java.util.function.Function.identity; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.tuple; -import static org.prebid.server.bidder.model.BidderError.Type.bad_server_response; - -public class AdoceanBidderTest extends VertxTest { - - private static final String ENDPOINT_URL = "https://{{Host}}"; - - private final AdoceanBidder target = new AdoceanBidder(ENDPOINT_URL, jacksonMapper); - - @Test - public void creationShouldFailOnInvalidEndpointUrl() { - assertThatIllegalArgumentException().isThrownBy(() -> new AdoceanBidder("invalid_url", jacksonMapper)); - } - - @Test - public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { - // given - final BidRequest bidRequest = givenBidRequest( - impBuilder -> impBuilder - .id("123") - .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))); - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()) - .containsExactly(BidderError.badInput("Error parsing adOceanExt parameters, in imp with id : 123")); - } - - @Test - public void makeHttpRequestsShouldReturnErrorIfEndpointUrlComposingFails() { - // given - final BidRequest bidRequest = BidRequest.builder() - .user(User.builder() - .ext(ExtUser.builder() - .consent("consent").build()) - .build()) - .imp(singletonList(Imp.builder() - .id("ao-test") - .banner(Banner.builder().build()) - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpAdocean.of("invalid domain", "masterId", - "adoceanmyaozpniqismex")))).build())) - .test(1) - .build(); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).hasSize(1) - .allSatisfy(error -> { - assertThat(error.getMessage()).startsWith("Invalid url: https://invalid domain/"); - assertThat(error.getType()).isEqualTo(BidderError.Type.bad_input); - }); - } - - @Test - public void makeHttpRequestsShouldReturnErrorIfExtImpEmitterPrefixIsEmpty() { - // given - final BidRequest bidRequest = BidRequest.builder() - .user(User.builder() - .ext(ExtUser.builder() - .consent("consent").build()) - .build()) - .imp(singletonList(Imp.builder() - .id("ao-test") - .banner(Banner.builder().build()) - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpAdocean.of("", "masterId", "adoceanmyaozpniqismex")))).build())) - .test(1) - .build(); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).isEmpty(); - assertThat(result.getErrors()).containsExactly(BidderError.badInput("No emitterPrefix param")); - } - - @Test - public void makeHttpRequestsShouldReturnErrorIfExtImpEmitterPrefixIsNotSupplied() { - // given - final BidRequest bidRequest = BidRequest.builder() - .user(User.builder() - .ext(ExtUser.builder() - .consent("consent").build()) - .build()) - .imp(singletonList(Imp.builder() - .id("ao-test") - .banner(Banner.builder().build()) - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpAdocean.of(null, "masterId", "adoceanmyaozpniqismex")))).build())) - .test(1) - .build(); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).isEmpty(); - assertThat(result.getErrors()).containsExactly(BidderError.badInput("No emitterPrefix param")); - } - - @Test - public void makeHttpRequestsShouldCreateRequestForEveryValidImp() { - // given - final BidRequest bidRequest = BidRequest.builder() - .user(User.builder() - .buyeruid("testBuyerUid") - .ext(ExtUser.builder() - .consent("consent").build()) - .build()) - .imp(asList(Imp.builder() - .id("ao-test") - .banner(Banner.builder().format(asList(Format.builder().h(250).w(300).build(), - Format.builder().h(320).w(600).build())).build()) - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpAdocean.of("myao.adocean.pl", "masterId", - "adoceanmyaozpniqismex")))).build(), - Imp.builder() - .id("notValidImp") - .ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode()))).build(), - Imp.builder() - .id("i2-test") - .banner(Banner.builder().w(577).h(333).build()) - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpAdocean.of("em.dom", "masterId2", - "slaveId")))).build())) - .test(1) - .build(); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()) - .containsExactly(BidderError.badInput("Error parsing adOceanExt parameters, " - + "in imp with id : notValidImp")); - assertThat(result.getValue()).hasSize(2) - .extracting(HttpRequest::getUri) - .containsExactly("https://myao.adocean.pl/_10000000/ad.json?pbsrv_v=1.3.0&id=masterId&nc=1" - + "&nosecure=1&aid=adoceanmyaozpniqismex%3Aao-test&gdpr_consent=consent&gdpr=1" - + "&hcuserid=testBuyerUid&aosspsizes=myaozpniqismex" - + "%7E300x250_600x320", "https://em.dom/_10000000/ad.json?pbsrv_v=1.3.0&id=" - + "masterId2&nc=1&nosecure=1&aid=slaveId%3Ai2-test&gdpr_consent=consent&gdpr=1" - + "&hcuserid=testBuyerUid&aosspsizes=slaveId%7E577x333"); - } - - @Test - public void makeHttpRequestsShouldCreateUniqueRequestIfMasterIdEqualsAndSlaveIdExists() { - // given - final BidRequest bidRequest = BidRequest.builder() - .user(User.builder() - .buyeruid("testBuyerUid") - .ext(ExtUser.builder() - .consent("consent").build()) - .build()) - .imp(asList(Imp.builder() - .id("ao-test") - .banner(Banner.builder().format(asList(Format.builder().h(250).w(300).build(), - Format.builder().h(320).w(600).build())).build()) - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpAdocean.of("myao.adocean.pl", "masterId", - "slaveId")))).build(), - Imp.builder() - .id("i2-test") - .banner(Banner.builder().w(577).h(333).build()) - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpAdocean.of("em.dom", "masterId", - "slaveId")))).build())) - .test(1) - .build(); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).hasSize(2); - } - - @Test - public void makeHttpRequestsShouldCreateRequestWithoutSizeIfBannerSizesNotPresent() { - // given - final BidRequest bidRequest = BidRequest.builder() - .user(User.builder() - .ext(ExtUser.builder() - .consent("consent").build()) - .build()) - .imp(singletonList(Imp.builder() - .id("ao-test") - .banner(Banner.builder().build()) - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpAdocean.of("myao.adocean.pl", "masterId", - "adoceanmyaozpniqismex")))).build())) - .test(1) - .build(); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) - .extracting(HttpRequest::getUri) - .containsExactly("https://myao.adocean.pl/_10000000/ad.json?pbsrv_v=1.3.0&id=masterId&nc=1&nosecure=1" - + "&aid=adoceanmyaozpniqismex%3Aao-test&gdpr_consent=consent&gdpr=1"); - } - - @Test - public void makeHttpRequestsShouldUpdateRequestsForSimilarSlaveIds() { - // given - final BidRequest bidRequest = BidRequest.builder() - .user(User.builder() - .buyeruid("testBuyerUid") - .ext(ExtUser.builder() - .consent("consent").build()) - .build()) - .imp(asList(Imp.builder() - .id("ao-test") - .banner(Banner.builder().format(asList(Format.builder().h(250).w(300).build(), - Format.builder().h(320).w(600).build())).build()) - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpAdocean.of("myao.adocean.pl", "masterId", - "slaveId")))).build(), - Imp.builder() - .id("i2-test") - .banner(Banner.builder().w(577).h(333).build()) - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpAdocean.of("em.dom", "masterId", - "slaveId2")))).build())) - .test(1) - .build(); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getUri) - .containsExactlyInAnyOrder("https://myao.adocean.pl/_10000000/ad.json?pbsrv_v=1.3.0&id=masterId&nc=1" - + "&nosecure=1&aid=slaveId%3Aao-test&gdpr_consent=consent&gdpr=1&hcuserid=testBuyerUid" - + "&aosspsizes=slaveId%7E300x250_600x320&aid=slaveId2%3Ai2-test&aosspsizes=slaveId2%7E577x333"); - } - - @Test - public void makeHttpRequestsShouldSetExpectedHeadersIfDeviceIpIsPresent() { - // given - final BidRequest bidRequest = BidRequest.builder() - .user(User.builder() - .ext(ExtUser.builder() - .consent("consent").build()) - .build()) - .imp(singletonList(Imp.builder() - .id("ao-test") - .banner(Banner.builder().format(singletonList(Format.builder().w(300).h(250).build())) - .id("banner_id").build()) - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpAdocean.of("myao.adocean.pl", - "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", - "adoceanmyaozpniqismex")))) - .build())) - .test(1) - .device(Device.builder().ip("192.168.1.1").build()) - .site(Site.builder().page("http://www.example.com").build()) - .build(); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue().getFirst().getHeaders()).isNotNull() - .extracting(Map.Entry::getKey, Map.Entry::getValue) - .containsExactly(tuple(HttpUtil.CONTENT_TYPE_HEADER.toString(), HttpUtil.APPLICATION_JSON_CONTENT_TYPE), - tuple(HttpUtil.ACCEPT_HEADER.toString(), HttpHeaderValues.APPLICATION_JSON.toString()), - tuple(HttpUtil.X_FORWARDED_FOR_HEADER.toString(), "192.168.1.1"), - tuple(HttpUtil.REFERER_HEADER.toString(), "http://www.example.com")); - } - - @Test - public void makeHttpRequestsShouldSetExpectedHeadersIfDeviceIpv6IsPresent() { - // given - final BidRequest bidRequest = BidRequest.builder() - .user(User.builder() - .ext(ExtUser.builder() - .consent("consent").build()) - .build()) - .imp(singletonList(Imp.builder() - .id("ao-test") - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpAdocean.of("myao.adocean.pl", "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", - "adoceanmyaozpniqismex")))) - .build())) - .test(1) - .device(Device.builder().ipv6("2001:0db8:85a3:0000:0000:8a2e:0370:7334").build()) - .site(Site.builder().page("http://www.example.com").build()) - .build(); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue().getFirst().getHeaders()).isNotNull() - .extracting(Map.Entry::getKey, Map.Entry::getValue) - .containsExactly(tuple(HttpUtil.CONTENT_TYPE_HEADER.toString(), HttpUtil.APPLICATION_JSON_CONTENT_TYPE), - tuple(HttpUtil.ACCEPT_HEADER.toString(), HttpHeaderValues.APPLICATION_JSON.toString()), - tuple(HttpUtil.X_FORWARDED_FOR_HEADER.toString(), "2001:0db8:85a3:0000:0000:8a2e:0370:7334"), - tuple(HttpUtil.REFERER_HEADER.toString(), "http://www.example.com")); - } - - @Test - public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() throws JsonProcessingException { - // given - final BidderCall httpCall = givenHttpCall(null, ""); - - // when - final Result> result = target.makeBids(httpCall, null); - - // then - assertThat(result.getErrors()).hasSize(1) - .allSatisfy(error -> { - assertThat(error.getType()).isEqualTo(bad_server_response); - assertThat(error.getMessage()) - .startsWith("Failed to decode: No content to map due to end-of-input"); - }); - assertThat(result.getValue()).isEmpty(); - } - - @Test - public void makeBidsShouldReturnCorrectBidderBid() throws JsonProcessingException { - // given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("impId") - .build())) - .build(); - final List adoceanResponseAdUnit = asList(adoceanResponseCreator(identity()), - adoceanResponseCreator(response -> response.id("adoceanmyaozpniqis"))); - - final BidderCall httpCall = givenHttpCall(null, mapper.writeValueAsString(adoceanResponseAdUnit)); - - // when - final Result> result = target.makeBids(httpCall, bidRequest); - - // then - final String adm = """ - \s""".formatted("https://win-url.com", "https://stats-url.com"); - - final BidderBid expected = BidderBid.of( - Bid.builder() - .id("ad") - .impid("ao-test") - .adm(adm) - .price(BigDecimal.valueOf(1)) - .crid("0af345b42983cc4bc0") - .w(300) - .h(250) - .build(), - BidType.banner, "EUR"); - assertThat(result.getValue().getFirst().getBid().getAdm()).isEqualTo(adm); - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).doesNotContainNull().hasSize(1).element(0).isEqualTo(expected); - } - - @Test - public void makeBidsShouldReturnEmptyListOfBids() throws JsonProcessingException { - // given - final BidRequest bidRequest = BidRequest.builder() - .imp(singletonList(Imp.builder() - .id("impId") - .build())) - .build(); - final List adoceanResponseAdUnit = asList( - adoceanResponseCreator(response -> response.error("true")), - adoceanResponseCreator(response -> response.id("adoceanmyaozpniqis"))); - - final BidderCall httpCall = givenHttpCall(null, mapper.writeValueAsString(adoceanResponseAdUnit)); - - // when - final Result> result = target.makeBids(httpCall, bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).isEqualTo(Collections.emptyList()); - } - - @Test - public void makeHttpRequestsShouldBuildUrlIfAppIsPresent() { - // given - final BidRequest bidRequest = BidRequest.builder() - .user(User.builder() - .ext(ExtUser.builder() - .consent("consent").build()) - .build()) - .imp(singletonList(Imp.builder() - .id("ao-test") - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpAdocean.of("myao.adocean.pl", "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", - "adoceanmyaozpniqismex")))) - .build())) - .test(1) - .app(App.builder().name("name").bundle("bundle").domain("domain").build()) - .build(); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getUri) - .containsExactlyInAnyOrder("https://myao.adocean.pl/_10000000/ad.json?pbsrv_v=1.3.0" - + "&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7&nc=1&nosecure=1" - + "&aid=adoceanmyaozpniqismex%3Aao-test&gdpr_consent=consent" - + "&gdpr=1&app=1&appname=name&appbundle=bundle&appdomain=domain"); - } - - @Test - public void makeHttpRequestsShouldBuildUrlIfDeviceWithIfaIsPresent() { - // given - final BidRequest bidRequest = BidRequest.builder() - .user(User.builder() - .ext(ExtUser.builder() - .consent("consent").build()) - .build()) - .imp(singletonList(Imp.builder() - .id("ao-test") - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpAdocean.of("myao.adocean.pl", "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", - "adoceanmyaozpniqismex")))) - .build())) - .test(1) - .device(Device.builder().ifa("ifa").os("os").osv("osv").model("model").make("make").build()) - .build(); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getUri) - .containsExactlyInAnyOrder("https://myao.adocean.pl/_10000000/ad.json?pbsrv_v=1.3.0" - + "&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7" - + "&nc=1&nosecure=1&aid=adoceanmyaozpniqismex%3Aao-test" - + "&gdpr_consent=consent&gdpr=1&ifa=ifa&devos=os&devosv=osv&devmodel=model&devmake=make"); - } - - @Test - public void makeHttpRequestsShouldBuildUrlIfDeviceWithIfaIsNotPresent() { - // given - final BidRequest bidRequest = BidRequest.builder() - .user(User.builder() - .ext(ExtUser.builder() - .consent("consent").build()) - .build()) - .imp(singletonList(Imp.builder() - .id("ao-test") - .ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpAdocean.of("myao.adocean.pl", "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", - "adoceanmyaozpniqismex")))) - .build())) - .test(1) - .device(Device.builder().dpidmd5("dpidmd5").os("os").osv("osv").model("model").make("make").build()) - .build(); - - // when - final Result>> result = target.makeHttpRequests(bidRequest); - - // then - assertThat(result.getValue()).hasSize(1) - .extracting(HttpRequest::getUri) - .containsExactlyInAnyOrder("https://myao.adocean.pl/_10000000/ad.json?pbsrv_v=1.3.0" - + "&id=tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7" - + "&nc=1&nosecure=1&aid=adoceanmyaozpniqismex%3Aao-test" - + "&gdpr_consent=consent&gdpr=1&dpidmd5=dpidmd5&devos=os&devosv=osv" - + "&devmodel=model&devmake=make"); - } - - private static AdoceanResponseAdUnit adoceanResponseCreator( - Function adoceanCustomizer) { - return adoceanCustomizer.apply(AdoceanResponseAdUnit.builder() - .id("ad") - .price("1") - .winUrl("https://win-url.com") - .statsUrl("https://stats-url.com") - .code(" ") - .currency("EUR") - .width("300") - .height("250") - .crid("0af345b42983cc4bc0") - .error("false")) - .build(); - } - - private static BidRequest givenBidRequest( - Function bidRequestCustomizer, - Function impCustomizer) { - - return bidRequestCustomizer.apply(BidRequest.builder() - .imp(singletonList(givenImp(impCustomizer)))) - .build(); - } - - private static BidRequest givenBidRequest(Function impCustomizer) { - return givenBidRequest(identity(), impCustomizer); - } - - private static Imp givenImp(Function impCustomizer) { - return impCustomizer.apply(Imp.builder() - .id("123") - .banner(Banner.builder().id("banner_id").build()).ext(mapper.valueToTree(ExtPrebid.of(null, - ExtImpAdocean.of("myao.adocean.pl", "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", - "adoceanmyaozpniqismex"))))) - .build(); - } - - private static BidderCall givenHttpCall(String requestBody, String responseBody) - throws JsonProcessingException { - return BidderCall.succeededHttp( - HttpRequest.builder() - .body(mapper.writeValueAsBytes(requestBody)) - .uri("https://myao.adocean.pl/_10000000/ad.json?aid=ad%3Aao-test&gdpr=1&gdpr_consent=consent" - + "&nc=1&nosecure=1&pbsrv_v=1.0.0") - .build(), - HttpResponse.of(200, null, responseBody), null); - } -} diff --git a/src/test/java/org/prebid/server/bidder/smarthub/SmarthubBidderTest.java b/src/test/java/org/prebid/server/bidder/smarthub/SmarthubBidderTest.java index 3f5630718e2..c9defbbd2e8 100644 --- a/src/test/java/org/prebid/server/bidder/smarthub/SmarthubBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/smarthub/SmarthubBidderTest.java @@ -17,7 +17,6 @@ import org.prebid.server.bidder.model.HttpResponse; import org.prebid.server.bidder.model.Result; import org.prebid.server.proto.openrtb.ext.ExtPrebid; -import org.prebid.server.proto.openrtb.ext.request.adocean.ExtImpAdocean; import org.prebid.server.proto.openrtb.ext.request.smarthub.ExtImpSmarthub; import org.prebid.server.proto.openrtb.ext.response.BidType; import org.prebid.server.proto.openrtb.ext.response.ExtBidResponse; @@ -178,7 +177,7 @@ public void makeBidsShouldReturnErrorIfExtIncorrect() throws JsonProcessingExcep // given final BidderCall httpCall = givenHttpCall(givenBidRequest(identity()), mapper.writeValueAsString(givenBidResponse(builder -> builder.ext(mapper.valueToTree( - ExtPrebid.of(null, ExtImpAdocean.of("someEmitterDomain", "someMasterId", "someSlaveID"))))))); + ExtPrebid.of(null, ExtImpSmarthub.of("someEmitterDomain", "someMasterId", "someSlaveID"))))))); // when final Result> result = target.makeBids(httpCall, null); diff --git a/src/test/java/org/prebid/server/it/AdoceanTest.java b/src/test/java/org/prebid/server/it/AdoceanTest.java deleted file mode 100644 index d1b0da1f1fc..00000000000 --- a/src/test/java/org/prebid/server/it/AdoceanTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.prebid.server.it; - -import com.github.tomakehurst.wiremock.client.WireMock; -import io.restassured.response.Response; -import org.json.JSONException; -import org.junit.jupiter.api.Test; -import org.prebid.server.model.Endpoint; - -import java.io.IOException; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static java.util.Collections.singletonList; - -public class AdoceanTest extends IntegrationTest { - - @Test - public void openrtb2AuctionShouldRespondWithBidsFromAdocean() throws IOException, JSONException { - - WIRE_MOCK_RULE.stubFor(get(WireMock.urlPathMatching("/adocean-exchange/_[0-9]*/ad.json")) - .willReturn(aResponse().withBody(jsonFrom("openrtb2/adocean/test-adocean-bid-response-1.json")))); - - // when - final Response response = responseFor("openrtb2/adocean/test-auction-adocean-request.json", - Endpoint.openrtb2_auction); - - // then - assertJsonEquals("openrtb2/adocean/test-auction-adocean-response.json", response, singletonList("adocean")); - } -} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adocean/test-adocean-bid-response-1.json b/src/test/resources/org/prebid/server/it/openrtb2/adocean/test-adocean-bid-response-1.json deleted file mode 100644 index 24304dd8da4..00000000000 --- a/src/test/resources/org/prebid/server/it/openrtb2/adocean/test-adocean-bid-response-1.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "id": "adoceanmyaozpniqismex", - "price": "10", - "winurl": "https://win-url.com", - "statsUrl": "https://stats-url.com", - "code": " ", - "currency": "USD", - "width": "300", - "height": "250", - "crid": "0af345b42983cc4bc0", - "error": "false" - } -] \ No newline at end of file diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adocean/test-auction-adocean-request.json b/src/test/resources/org/prebid/server/it/openrtb2/adocean/test-auction-adocean-request.json deleted file mode 100644 index eccdf164b32..00000000000 --- a/src/test/resources/org/prebid/server/it/openrtb2/adocean/test-auction-adocean-request.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "id": "request_id", - "imp": [ - { - "id": "imp_id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "prebid": { - "bidder": { - "adocean": { - "emitterPrefix": "myao", - "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", - "slaveId": "adoceanmyaozpniqismex" - } - } - } - } - } - ], - "tmax": 5000, - "regs": { - "ext": { - "gdpr": 0 - } - } -} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/adocean/test-auction-adocean-response.json b/src/test/resources/org/prebid/server/it/openrtb2/adocean/test-auction-adocean-response.json deleted file mode 100644 index ea7c23c5bb4..00000000000 --- a/src/test/resources/org/prebid/server/it/openrtb2/adocean/test-auction-adocean-response.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "id": "request_id", - "seatbid": [ - { - "bid": [ - { - "id": "adoceanmyaozpniqismex", - "impid": "imp_id", - "exp": 300, - "price": 10, - "adm": " ", - "crid": "0af345b42983cc4bc0", - "w": 300, - "h": 250, - "ext": { - "prebid": { - "type": "banner", - "meta": { - "adaptercode": "adocean" - } - }, - "origbidcpm": 10, - "origbidcur": "USD" - } - } - ], - "seat": "adocean", - "group": 0 - } - ], - "cur": "USD", - "ext": { - "responsetimemillis": { - "adocean": "{{ adocean.response_time_ms }}" - }, - "tmaxrequest": 5000, - "prebid": { - "auctiontimestamp": 0 - } - } -} diff --git a/src/test/resources/org/prebid/server/it/test-application.properties b/src/test/resources/org/prebid/server/it/test-application.properties index c6243333b71..f58858f44f2 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -62,8 +62,6 @@ adapters.admixer.endpoint=http://localhost:8090/admixer-exchange adapters.adnuntius.enabled=true adapters.adnuntius.endpoint=http://localhost:8090/adnuntius-exchange adapters.adnuntius.eu-endpoint=http://localhost:8090/adnuntius-exchange-eu -adapters.adocean.enabled=true -adapters.adocean.endpoint=http://localhost:8090/adocean-exchange adapters.elementaltv.enabled=true adapters.elementaltv.endpoint=http://localhost:8090/elementaltv-exchange adapters.adpone.enabled=true From db7a3ae0c5a0d673d2abe479ceb3ba12ae4628a9 Mon Sep 17 00:00:00 2001 From: przemkaczmarek <167743744+przemkaczmarek@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:34:32 +0100 Subject: [PATCH 29/38] Adot: Constrain publisher path to an enum list (#4354) --- src/main/resources/static/bidder-params/adot.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/static/bidder-params/adot.json b/src/main/resources/static/bidder-params/adot.json index e82b17be8d4..45caca4da80 100644 --- a/src/main/resources/static/bidder-params/adot.json +++ b/src/main/resources/static/bidder-params/adot.json @@ -14,7 +14,8 @@ }, "publisherPath": { "type": "string", - "description": "An optional path used in the bid endpoint" + "enum": ["/hubvisor", "/spotx"], + "description": "An optional path used in the bid endpoint. Only '/hubvisor' and '/spotx' are allowed." } }, "required": [] From 9e9bf59404062aa21738297c3c4af9a95958ea98 Mon Sep 17 00:00:00 2001 From: przemkaczmarek <167743744+przemkaczmarek@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:34:45 +0100 Subject: [PATCH 30/38] New Adapter: Clydo (#4299) --- .../server/bidder/clydo/ClydoBidder.java | 182 +++++++++ .../ext/request/clydo/ExtImpClydo.java | 13 + .../config/bidder/ClydoConfiguration.java | 41 ++ src/main/resources/bidder-config/clydo.yaml | 21 + .../resources/static/bidder-params/clydo.json | 20 + .../server/bidder/clydo/ClydoBidderTest.java | 383 ++++++++++++++++++ .../java/org/prebid/server/it/ClydoTest.java | 32 ++ .../clydo/test-auction-clydo-request.json | 24 ++ .../clydo/test-auction-clydo-response.json | 42 ++ .../clydo/test-clydo-bid-request.json | 57 +++ .../clydo/test-clydo-bid-response.json | 20 + .../server/it/test-application.properties | 2 + 12 files changed, 837 insertions(+) create mode 100644 src/main/java/org/prebid/server/bidder/clydo/ClydoBidder.java create mode 100644 src/main/java/org/prebid/server/proto/openrtb/ext/request/clydo/ExtImpClydo.java create mode 100644 src/main/java/org/prebid/server/spring/config/bidder/ClydoConfiguration.java create mode 100644 src/main/resources/bidder-config/clydo.yaml create mode 100644 src/main/resources/static/bidder-params/clydo.json create mode 100644 src/test/java/org/prebid/server/bidder/clydo/ClydoBidderTest.java create mode 100644 src/test/java/org/prebid/server/it/ClydoTest.java create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/clydo/test-auction-clydo-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/clydo/test-auction-clydo-response.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/clydo/test-clydo-bid-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/clydo/test-clydo-bid-response.json diff --git a/src/main/java/org/prebid/server/bidder/clydo/ClydoBidder.java b/src/main/java/org/prebid/server/bidder/clydo/ClydoBidder.java new file mode 100644 index 00000000000..ca7b04ee32f --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/clydo/ClydoBidder.java @@ -0,0 +1,182 @@ +package org.prebid.server.bidder.clydo; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Device; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.response.Bid; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; +import io.netty.handler.codec.http.HttpHeaderValues; +import io.vertx.core.MultiMap; +import org.prebid.server.bidder.Bidder; +import org.prebid.server.bidder.model.BidderBid; +import org.prebid.server.bidder.model.BidderCall; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.HttpRequest; +import org.prebid.server.bidder.model.Result; +import org.prebid.server.exception.PreBidException; +import org.prebid.server.json.DecodeException; +import org.prebid.server.json.JacksonMapper; +import org.prebid.server.proto.openrtb.ext.ExtPrebid; +import org.prebid.server.proto.openrtb.ext.request.clydo.ExtImpClydo; +import org.prebid.server.proto.openrtb.ext.response.BidType; +import org.prebid.server.util.BidderUtil; +import org.prebid.server.util.HttpUtil; +import org.prebid.server.util.ObjectUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +public class ClydoBidder implements Bidder { + + private static final TypeReference> CLYDO_EXT_TYPE_REFERENCE = + new TypeReference<>() { + }; + + private static final String REGION_MACRO = "{{Region}}"; + private static final String PARTNER_ID_MACRO = "{{PartnerId}}"; + private static final String DEFAULT_REGION = "us"; + private static final String X_OPENRTB_VERSION = "2.5"; + + private final String endpointUrl; + private final JacksonMapper mapper; + + public ClydoBidder(String endpointUrl, JacksonMapper mapper) { + this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); + this.mapper = Objects.requireNonNull(mapper); + } + + @Override + public Result>> makeHttpRequests(BidRequest request) { + final List errors = new ArrayList<>(); + final List> httpRequests = new ArrayList<>(); + + for (Imp imp : request.getImp()) { + try { + final ExtImpClydo extImpClydo = parseExtImp(imp); + final HttpRequest httpRequest = makeHttpRequest(request, imp, extImpClydo); + httpRequests.add(httpRequest); + } catch (PreBidException e) { + errors.add(BidderError.badInput(e.getMessage())); + } + } + + if (httpRequests.isEmpty()) { + return Result.withError(BidderError.badInput("found no valid impressions")); + } + + return Result.of(httpRequests, errors); + } + + private ExtImpClydo parseExtImp(Imp imp) { + try { + return mapper.mapper().convertValue(imp.getExt(), CLYDO_EXT_TYPE_REFERENCE).getBidder(); + } catch (IllegalArgumentException e) { + throw new PreBidException("Cannot deserialize ExtImpClydo: " + e.getMessage()); + } + } + + private HttpRequest makeHttpRequest(BidRequest request, Imp imp, ExtImpClydo extImpClydo) { + final BidRequest outgoingRequest = request.toBuilder().imp(List.of(imp)).build(); + + return BidderUtil.defaultRequest( + outgoingRequest, + constructHeaders(request), + resolveUrl(endpointUrl, extImpClydo), mapper); + } + + private static MultiMap constructHeaders(BidRequest bidRequest) { + final Device device = bidRequest.getDevice(); + final MultiMap headers = HttpUtil.headers(); + + headers.set(HttpUtil.X_OPENRTB_VERSION_HEADER, X_OPENRTB_VERSION); + headers.set(HttpUtil.ACCEPT_HEADER, HttpHeaderValues.APPLICATION_JSON); + headers.set(HttpUtil.CONTENT_TYPE_HEADER, HttpUtil.APPLICATION_JSON_CONTENT_TYPE); + HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, + ObjectUtil.getIfNotNull(device, Device::getIpv6)); + HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, + ObjectUtil.getIfNotNull(device, Device::getIp)); + HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.USER_AGENT_HEADER, + ObjectUtil.getIfNotNull(device, Device::getUa)); + + return headers; + } + + private static String resolveUrl(String endpoint, ExtImpClydo extImp) { + return endpoint + .replace(REGION_MACRO, getRegionInfo(extImp)) + .replace(PARTNER_ID_MACRO, HttpUtil.encodeUrl(extImp.getPartnerId())); + } + + private static String getRegionInfo(ExtImpClydo extImp) { + final String region = extImp.getRegion(); + + return switch (region) { + case "us", "usw", "eu", "apac" -> region; + case null, default -> DEFAULT_REGION; + }; + } + + @Override + public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { + try { + final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); + final List bidderBids = extractBids(bidRequest, bidResponse); + return Result.withValues(bidderBids); + } catch (DecodeException | PreBidException e) { + return Result.withError(BidderError.badServerResponse(e.getMessage())); + } + } + + private List extractBids(BidRequest bidRequest, BidResponse bidResponse) { + if (bidResponse == null || bidResponse.getSeatbid() == null || bidResponse.getSeatbid().isEmpty()) { + return Collections.emptyList(); + } + + final Map impIdToBidTypeMap = buildImpIdToBidTypeMap(bidRequest); + + return bidResponse.getSeatbid().stream() + .filter(Objects::nonNull) + .map(SeatBid::getBid) + .filter(Objects::nonNull) + .flatMap(List::stream) + .filter(Objects::nonNull) + .map(bid -> BidderBid.of(bid, resolveBidType(bid, impIdToBidTypeMap), bidResponse.getCur())) + .collect(Collectors.toList()); + } + + private static Map buildImpIdToBidTypeMap(BidRequest bidRequest) { + final Map impIdToBidTypeMap = new HashMap<>(); + for (Imp imp : bidRequest.getImp()) { + final String impId = imp.getId(); + final BidType bidType = determineBidType(imp); + impIdToBidTypeMap.put(impId, bidType); + } + + return impIdToBidTypeMap; + } + + private static BidType determineBidType(Imp imp) { + if (imp.getAudio() != null) { + return BidType.audio; + } else if (imp.getVideo() != null) { + return BidType.video; + } else if (imp.getXNative() != null) { + return BidType.xNative; + } else if (imp.getBanner() != null) { + return BidType.banner; + } + + throw new PreBidException("Failed to get media type"); + } + + private static BidType resolveBidType(Bid bid, Map impIdToBidTypeMap) { + return impIdToBidTypeMap.getOrDefault(bid.getImpid(), BidType.banner); + } +} diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/clydo/ExtImpClydo.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/clydo/ExtImpClydo.java new file mode 100644 index 00000000000..68338055de4 --- /dev/null +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/clydo/ExtImpClydo.java @@ -0,0 +1,13 @@ +package org.prebid.server.proto.openrtb.ext.request.clydo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +@Value(staticConstructor = "of") +public class ExtImpClydo { + + @JsonProperty("partnerId") + String partnerId; + + String region; +} diff --git a/src/main/java/org/prebid/server/spring/config/bidder/ClydoConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/ClydoConfiguration.java new file mode 100644 index 00000000000..98cedfc8d08 --- /dev/null +++ b/src/main/java/org/prebid/server/spring/config/bidder/ClydoConfiguration.java @@ -0,0 +1,41 @@ +package org.prebid.server.spring.config.bidder; + +import org.prebid.server.bidder.BidderDeps; +import org.prebid.server.bidder.clydo.ClydoBidder; +import org.prebid.server.json.JacksonMapper; +import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties; +import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler; +import org.prebid.server.spring.config.bidder.util.UsersyncerCreator; +import org.prebid.server.spring.env.YamlPropertySourceFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +import jakarta.validation.constraints.NotBlank; + +@Configuration +@PropertySource(value = "classpath:/bidder-config/clydo.yaml", factory = YamlPropertySourceFactory.class) +public class ClydoConfiguration { + + private static final String BIDDER_NAME = "clydo"; + + @Bean("clydoConfigurationProperties") + @ConfigurationProperties("adapters.clydo") + BidderConfigurationProperties configurationProperties() { + return new BidderConfigurationProperties(); + } + + @Bean + BidderDeps clydoBidderDeps(BidderConfigurationProperties clydoConfigurationProperties, + @NotBlank @Value("${external-url}") String externalUrl, + JacksonMapper mapper) { + + return BidderDepsAssembler.forBidder(BIDDER_NAME) + .withConfig(clydoConfigurationProperties) + .usersyncerCreator(UsersyncerCreator.create(externalUrl)) + .bidderCreator(config -> new ClydoBidder(config.getEndpoint(), mapper)) + .assemble(); + } +} diff --git a/src/main/resources/bidder-config/clydo.yaml b/src/main/resources/bidder-config/clydo.yaml new file mode 100644 index 00000000000..aa90e3a4f07 --- /dev/null +++ b/src/main/resources/bidder-config/clydo.yaml @@ -0,0 +1,21 @@ +adapters: + clydo: + endpoint: http://region={{Region}}.clydo.io/partnerId={{PartnerId}} + modifying-vast-xml-allowed: true + endpoint-compression: gzip + geoscope: + - global + meta-info: + maintainer-email: cto@clydo.io + app-media-types: + - banner + - video + - audio + - native + site-media-types: + - banner + - video + - audio + - native + supported-vendors: + vendor-id: 0 diff --git a/src/main/resources/static/bidder-params/clydo.json b/src/main/resources/static/bidder-params/clydo.json new file mode 100644 index 00000000000..71b60d09405 --- /dev/null +++ b/src/main/resources/static/bidder-params/clydo.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Clydo Adapter Params", + "description": "A schema which validates params accepted by the Clydo adapter", + "type": "object", + "properties": { + "partnerId": { + "type": "string", + "description": "Partner ID", + "minLength": 1 + }, + "region": { + "type": "string", + "description": "Regional endpoint identifier (us, usw, eu, apac)", + "enum": ["us", "usw", "eu", "apac"] + } + }, + + "required": ["partnerId"] +} diff --git a/src/test/java/org/prebid/server/bidder/clydo/ClydoBidderTest.java b/src/test/java/org/prebid/server/bidder/clydo/ClydoBidderTest.java new file mode 100644 index 00000000000..d4fc86456f5 --- /dev/null +++ b/src/test/java/org/prebid/server/bidder/clydo/ClydoBidderTest.java @@ -0,0 +1,383 @@ +package org.prebid.server.bidder.clydo; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.iab.openrtb.request.Audio; +import com.iab.openrtb.request.Banner; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.Native; +import com.iab.openrtb.request.Video; +import com.iab.openrtb.response.Bid; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; +import org.junit.jupiter.api.Test; +import org.prebid.server.VertxTest; +import org.prebid.server.bidder.model.BidderBid; +import org.prebid.server.bidder.model.BidderCall; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.HttpRequest; +import org.prebid.server.bidder.model.HttpResponse; +import org.prebid.server.bidder.model.Result; +import org.prebid.server.proto.openrtb.ext.ExtPrebid; +import org.prebid.server.proto.openrtb.ext.request.clydo.ExtImpClydo; + +import java.util.List; +import java.util.function.UnaryOperator; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.prebid.server.proto.openrtb.ext.response.BidType.audio; +import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; +import static org.prebid.server.proto.openrtb.ext.response.BidType.video; +import static org.prebid.server.proto.openrtb.ext.response.BidType.xNative; +import static org.prebid.server.util.HttpUtil.ACCEPT_HEADER; +import static org.prebid.server.util.HttpUtil.APPLICATION_JSON_CONTENT_TYPE; +import static org.prebid.server.util.HttpUtil.CONTENT_TYPE_HEADER; +import static org.springframework.util.MimeTypeUtils.APPLICATION_JSON_VALUE; + +public class ClydoBidderTest extends VertxTest { + + private static final String ENDPOINT_URL = "http://region={{Region}}.clydo.io/partnerId={{PartnerId}}"; + + private final ClydoBidder target = new ClydoBidder(ENDPOINT_URL, jacksonMapper); + + @Test + public void creationShouldFailOnInvalidEndpointUrl() { + assertThatIllegalArgumentException().isThrownBy(() -> new ClydoBidder("invalid_url", jacksonMapper)); + } + + @Test + public void makeHttpRequestsShouldMakeOneRequestPerImp() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(asList( + givenImp(UnaryOperator.identity()), + givenImp(imp -> imp.id("321").ext(mapper.valueToTree(ExtPrebid + .of(null, ExtImpClydo.of("parentId", "us"))))))) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(2) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getImp) + .extracting(List::size) + .containsOnly(1); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getId) + .containsExactlyInAnyOrder("123", "321"); + } + + @Test + public void shouldMakeOneRequestWhenOneImpIsValidAndAnotherIsNot() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(asList(givenImp(UnaryOperator.identity()), givenBadImp(UnaryOperator.identity()))) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getId) + .containsExactly("123"); + } + + @Test + public void makeHttpRequestsShouldIncludeImpIds() { + // given + final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.id("imp1")); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getId) + .containsExactly("imp1"); + } + + @Test + public void makeHttpRequestsShouldUseCorrectUri() { + // given + final BidRequest bidRequest = givenBidRequest(UnaryOperator.identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getUri) + .containsExactly("http://region=us.clydo.io/partnerId=parentId"); + } + + @Test + public void makeHttpRequestsShouldReturnErrorIfImpExtCannotBeParsed() { + // given + final BidRequest bidRequest = givenBidRequest(impBuilder -> + impBuilder.ext(mapper.createObjectNode().set("bidder", mapper.createArrayNode()))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).hasSize(1); + assertThat(result.getErrors().getFirst().getMessage()).startsWith("found no valid impressions"); + } + + @Test + public void makeHttpRequestsShouldReturnExpectedHeaders() { + // given + final BidRequest bidRequest = givenBidRequest(UnaryOperator.identity()); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()).hasSize(1).first() + .extracting(HttpRequest::getHeaders) + .satisfies(headers -> assertThat(headers.get(CONTENT_TYPE_HEADER)) + .isEqualTo(APPLICATION_JSON_CONTENT_TYPE)) + .satisfies(headers -> assertThat(headers.get(ACCEPT_HEADER)) + .isEqualTo(APPLICATION_JSON_VALUE)); + assertThat(result.getErrors()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldUseRegionUsInEndpoint() { + // given + final BidRequest bidRequest = givenBidRequest(imp -> imp + .id("imp-us") + .ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpClydo.of("partner123", "us"))))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1).first() + .extracting(HttpRequest::getUri) + .isEqualTo("http://region=us.clydo.io/partnerId=partner123"); + } + + @Test + public void makeHttpRequestsShouldUseRegionEuInEndpointWithEuRegion() { + // given + final BidRequest bidRequest = givenBidRequest(imp -> imp + .id("imp-us") + .ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpClydo.of("partner123", "eu"))))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1).first() + .extracting(HttpRequest::getUri) + .isEqualTo("http://region=eu.clydo.io/partnerId=partner123"); + } + + @Test + public void makeHttpRequestsShouldUseDefaultRegionUsWhenRegionIsNull() { + // given + final BidRequest bidRequest = givenBidRequest(imp -> imp + .id("imp-null-region") + .ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpClydo.of("partner123", null))))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1).first() + .extracting(HttpRequest::getUri) + .isEqualTo("http://region=us.clydo.io/partnerId=partner123"); + } + + @Test + public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall( + mapper.writeValueAsString(null)); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { + // given + final BidderCall httpCall = givenHttpCall("invalid"); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).hasSize(1) + .allSatisfy(error -> { + assertThat(error.getMessage()).startsWith("Failed to decode: Unrecognized token 'invalid':"); + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response); + }); + } + + @Test + public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull() throws JsonProcessingException { + // given + final BidderCall httpCall = givenHttpCall( + mapper.writeValueAsString(BidResponse.builder().build())); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnBannerBid() throws JsonProcessingException { + // given + final BidRequest bidRequest = givenBidRequest(imp -> imp + .banner(Banner.builder().w(300).h(250).build())); + final BidderCall httpCall = givenHttpCall( + givenBidResponse(bidBuilder -> bidBuilder.impid("123"))); + + // when + final Result> result = target.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).extracting(BidderBid::getType).containsExactly(banner); + } + + @Test + public void makeBidsShouldReturnVideoBid() throws JsonProcessingException { + // given + final BidRequest bidRequest = givenBidRequest(imp -> imp + .video(Video.builder().mimes(singletonList("video/mp4")).build())); + final BidderCall httpCall = givenHttpCall(givenBidResponse(bidBuilder -> bidBuilder.impid("123"))); + + // when + final Result> result = target.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).extracting(BidderBid::getType).containsExactly(video); + } + + @Test + public void makeBidsShouldReturnNativeBid() throws JsonProcessingException { + // given + final BidRequest bidRequest = givenBidRequest(imp -> imp + .xNative(Native.builder().request("{\"assets\":[]}").build())); + final BidderCall httpCall = givenHttpCall(givenBidResponse(bidBuilder -> bidBuilder.impid("123"))); + + // when + final Result> result = target.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).extracting(BidderBid::getType).containsExactly(xNative); + } + + @Test + public void makeBidsShouldReturnAudioBid() throws JsonProcessingException { + // given + final BidRequest bidRequest = givenBidRequest(imp -> imp + .banner(null) + .audio(Audio.builder().mimes(singletonList("audio/mp3")).build())); + final BidderCall httpCall = givenHttpCall(givenBidResponse(bidBuilder -> bidBuilder.impid("123"))); + + // when + final Result> result = target.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).extracting(BidderBid::getType).containsExactly(audio); + } + + @Test + public void makeBidsShouldReturnErrorWhenImpHasNoMediaType() throws JsonProcessingException { + // given + final BidRequest bidRequest = givenBidRequest(imp -> imp.banner(null).video(null).xNative(null).audio(null)); + final BidderCall httpCall = givenHttpCall(givenBidResponse(bidBuilder -> bidBuilder.impid("123"))); + + // when + final Result> result = target.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getValue()).isEmpty(); + assertThat(result.getErrors()).hasSize(1).first() + .satisfies(error -> { + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response); + assertThat(error.getMessage()).isEqualTo("Failed to get media type"); + }); + } + + private static BidRequest givenBidRequest( + UnaryOperator bidRequestCustomizer, + UnaryOperator impCustomizer) { + + return bidRequestCustomizer.apply(BidRequest.builder() + .imp(singletonList(givenImp(impCustomizer)))) + .build(); + } + + private static BidRequest givenBidRequest(UnaryOperator impCustomizer) { + return givenBidRequest(UnaryOperator.identity(), impCustomizer); + } + + private static Imp givenImp(UnaryOperator impCustomizer) { + return impCustomizer.apply(Imp.builder() + .id("123") + .ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpClydo.of("parentId", "us"))))) + .build(); + } + + private static Imp givenBadImp(UnaryOperator impCustomizer) { + return impCustomizer.apply(Imp.builder() + .id("invalidImp") + .ext(mapper.createObjectNode().set("bidder", mapper.createArrayNode()))) + .build(); + } + + private static String givenBidResponse(UnaryOperator bidCustomizer) throws JsonProcessingException { + final BidResponse bidResponse = BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(singletonList(bidCustomizer.apply(Bid.builder()).build())) + .build())) + .build(); + return mapper.writeValueAsString(bidResponse); + } + + private static BidderCall givenHttpCall(String body) { + return BidderCall.succeededHttp( + HttpRequest.builder().payload(null).build(), + HttpResponse.of(200, null, body), + null); + } +} diff --git a/src/test/java/org/prebid/server/it/ClydoTest.java b/src/test/java/org/prebid/server/it/ClydoTest.java new file mode 100644 index 00000000000..6bb262350a3 --- /dev/null +++ b/src/test/java/org/prebid/server/it/ClydoTest.java @@ -0,0 +1,32 @@ +package org.prebid.server.it; + +import io.restassured.response.Response; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.prebid.server.model.Endpoint; + +import java.io.IOException; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static java.util.Collections.singletonList; + +public class ClydoTest extends IntegrationTest { + + @Test + public void openrtb2AuctionShouldRespondWithBidsFromClydo() throws IOException, JSONException { + // given + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/clydo-exchange")) + .withRequestBody(equalToJson(jsonFrom("openrtb2/clydo/test-clydo-bid-request.json"))) + .willReturn(aResponse().withBody(jsonFrom("openrtb2/clydo/test-clydo-bid-response.json")))); + + // when + final Response response = responseFor("openrtb2/clydo/test-auction-clydo-request.json", + Endpoint.openrtb2_auction); + + // then + assertJsonEquals("openrtb2/clydo/test-auction-clydo-response.json", response, singletonList("clydo")); + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/clydo/test-auction-clydo-request.json b/src/test/resources/org/prebid/server/it/openrtb2/clydo/test-auction-clydo-request.json new file mode 100644 index 00000000000..bfe3de91342 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/clydo/test-auction-clydo-request.json @@ -0,0 +1,24 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "clydo": { + "region": "us", + "partnerId": "testPartnerId" + } + } + } + ], + "tmax": 5000, + "regs": { + "ext": { + "gdpr": 0 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/clydo/test-auction-clydo-response.json b/src/test/resources/org/prebid/server/it/openrtb2/clydo/test-auction-clydo-response.json new file mode 100644 index 00000000000..4b1494175f4 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/clydo/test-auction-clydo-response.json @@ -0,0 +1,42 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "exp": 300, + "price": 3.33, + "adm": "adm001", + "adid": "adid", + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner", + "meta": { + "adaptercode": "clydo" + } + }, + "origbidcpm": 3.33 + } + } + ], + "seat": "clydo", + "group": 0 + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "clydo": "{{ clydo.response_time_ms }}" + }, + "prebid": { + "auctiontimestamp": 0 + }, + "tmaxrequest": 5000 + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/clydo/test-clydo-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/clydo/test-clydo-bid-request.json new file mode 100644 index 00000000000..e43823a0317 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/clydo/test-clydo-bid-request.json @@ -0,0 +1,57 @@ +{ + "id": "request_id", + "imp": [ + { + "id": "imp_id", + "secure": 1, + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "tid": "${json-unit.any-string}", + "bidder": { + "region": "us", + "partnerId": "testPartnerId" + } + } + } + ], + "source": { + "tid": "${json-unit.any-string}" + }, + "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": "${json-unit.any-number}", + "cur": [ + "USD" + ], + "regs": { + "ext": { + "gdpr": 0 + } + }, + "ext": { + "prebid": { + "server": { + "externalurl": "http://localhost:8080", + "gvlid": 1, + "datacenter": "local", + "endpoint": "/openrtb2/auction" + } + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/clydo/test-clydo-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/clydo/test-clydo-bid-response.json new file mode 100644 index 00000000000..46fdbbec2ad --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/clydo/test-clydo-bid-response.json @@ -0,0 +1,20 @@ +{ + "id": "request_id", + "seatbid": [ + { + "bid": [ + { + "id": "bid_id", + "impid": "imp_id", + "price": 3.33, + "adid": "adid", + "crid": "crid", + "cid": "cid", + "adm": "adm001", + "h": 250, + "w": 300 + } + ] + } + ] +} diff --git a/src/test/resources/org/prebid/server/it/test-application.properties b/src/test/resources/org/prebid/server/it/test-application.properties index f58858f44f2..439ef436125 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -187,6 +187,8 @@ adapters.brave.enabled=true adapters.brave.endpoint=http://localhost:8090/brave-exchange adapters.bwx.enabled=true adapters.bwx.endpoint=http://localhost:8090/bwx-exchange +adapters.clydo.enabled=true +adapters.clydo.endpoint=http://localhost:8090/clydo-exchange adapters.cointraffic.enabled=true adapters.cointraffic.endpoint=http://localhost:8090/cointraffic-exchange adapters.connatix.enabled=true From 66d7d8ada8a0ed6cb3524281126ef20ba2cd4aca Mon Sep 17 00:00:00 2001 From: przemkaczmarek <167743744+przemkaczmarek@users.noreply.github.com> Date: Thu, 5 Feb 2026 14:11:55 +0100 Subject: [PATCH 31/38] Aso: Add user sync endpoint to adapter and aliases (#4353) --- src/main/resources/bidder-config/aso.yaml | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/main/resources/bidder-config/aso.yaml b/src/main/resources/bidder-config/aso.yaml index fa6a6381741..5f3bdad4829 100644 --- a/src/main/resources/bidder-config/aso.yaml +++ b/src/main/resources/bidder-config/aso.yaml @@ -7,16 +7,34 @@ adapters: endpoint: https://srv.datacygnal.io/pbs/bidder?zid={{ZoneID}} meta-info: maintainer-email: contact@bcm.ltd + usersync: + cookie-family-name: bcmint + redirect: + url: https://track.datacygnal.io/sync/v2?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&usp={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&redir={{redirect_url}} + support-cors: false + uid-macro: '{uid}' bidagency: enabled: false endpoint: https://srv.bidgx.com/pbs/bidder?zid={{ZoneID}} meta-info: maintainer-email: aso@bidgency.com + usersync: + cookie-family-name: bidagency + redirect: + url: https://track.bidgx.com/sync/v2?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&usp={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&redir={{redirect_url}} + support-cors: false + uid-macro: '{uid}' kuantyx: enabled: false endpoint: https://srv.kntxy.com/pbs/bidder?zid={{ZoneID}} meta-info: maintainer-email: ssp@kuantyx.com + usersync: + cookie-family-name: kuantyx + redirect: + url: https://track.kntxy.com/sync/v2?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&usp={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&redir={{redirect_url}} + support-cors: false + uid-macro: '{uid}' meta-info: maintainer-email: support@adsrv.org app-media-types: @@ -28,3 +46,9 @@ adapters: - video - native vendor-id: 0 + usersync: + cookie-family-name: aso + redirect: + url: https://track.aso1.net/sync/v2?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&usp={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&redir={{redirect_url}} + support-cors: false + uid-macro: '{uid}' From a974b78fe881548b62115f3c9365a433526a0614 Mon Sep 17 00:00:00 2001 From: ollyburns <75783836+ollyburns@users.noreply.github.com> Date: Thu, 5 Feb 2026 13:17:13 +0000 Subject: [PATCH 32/38] Teal: new adapter (#4350) --- .../prebid/server/bidder/teal/TealBidder.java | 200 +++++++++++++ .../openrtb/ext/request/teal/ExtImpTeal.java | 11 + .../config/bidder/TealConfiguration.java | 42 +++ src/main/resources/bidder-config/teal.yaml | 30 ++ .../resources/static/bidder-params/teal.json | 19 ++ .../server/bidder/teal/TealBidderTest.java | 273 ++++++++++++++++++ .../java/org/prebid/server/it/TealTest.java | 32 ++ .../teal/test-auction-teal-request.json | 44 +++ .../teal/test-auction-teal-response.json | 41 +++ .../openrtb2/teal/test-teal-bid-request.json | 77 +++++ .../openrtb2/teal/test-teal-bid-response.json | 29 ++ .../server/it/test-application.properties | 2 + 12 files changed, 800 insertions(+) create mode 100644 src/main/java/org/prebid/server/bidder/teal/TealBidder.java create mode 100644 src/main/java/org/prebid/server/proto/openrtb/ext/request/teal/ExtImpTeal.java create mode 100644 src/main/java/org/prebid/server/spring/config/bidder/TealConfiguration.java create mode 100644 src/main/resources/bidder-config/teal.yaml create mode 100644 src/main/resources/static/bidder-params/teal.json create mode 100644 src/test/java/org/prebid/server/bidder/teal/TealBidderTest.java create mode 100644 src/test/java/org/prebid/server/it/TealTest.java create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/teal/test-auction-teal-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/teal/test-auction-teal-response.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/teal/test-teal-bid-request.json create mode 100644 src/test/resources/org/prebid/server/it/openrtb2/teal/test-teal-bid-response.json diff --git a/src/main/java/org/prebid/server/bidder/teal/TealBidder.java b/src/main/java/org/prebid/server/bidder/teal/TealBidder.java new file mode 100644 index 00000000000..c1869421dd8 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/teal/TealBidder.java @@ -0,0 +1,200 @@ +package org.prebid.server.bidder.teal; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.App; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.Publisher; +import com.iab.openrtb.request.Site; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.prebid.server.bidder.Bidder; +import org.prebid.server.bidder.model.BidderBid; +import org.prebid.server.bidder.model.BidderCall; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.HttpRequest; +import org.prebid.server.bidder.model.Result; +import org.prebid.server.exception.PreBidException; +import org.prebid.server.json.DecodeException; +import org.prebid.server.json.JacksonMapper; +import org.prebid.server.proto.openrtb.ext.ExtPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.teal.ExtImpTeal; +import org.prebid.server.proto.openrtb.ext.response.BidType; +import org.prebid.server.util.BidderUtil; +import org.prebid.server.util.HttpUtil; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public class TealBidder implements Bidder { + + private static final TypeReference> TYPE_REFERENCE = new TypeReference<>() { + }; + + private final String endpointUrl; + private final JacksonMapper mapper; + + public TealBidder(String endpointUrl, JacksonMapper mapper) { + this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); + this.mapper = Objects.requireNonNull(mapper); + } + + @Override + public Result>> makeHttpRequests(BidRequest request) { + final List modifiedImps = new ArrayList<>(); + final List errors = new ArrayList<>(); + String account = null; + + for (Imp imp : request.getImp()) { + final ExtImpTeal extImpTeal; + try { + extImpTeal = parseImpExt(imp); + validateImpExt(extImpTeal); + } catch (PreBidException e) { + errors.add(BidderError.badInput(e.getMessage())); + continue; + } + + account = account == null ? extImpTeal.getAccount() : account; + modifiedImps.add(modifyImp(imp, extImpTeal.getPlacement())); + } + + if (modifiedImps.isEmpty()) { + return Result.withErrors(errors); + } + + final BidRequest modifiedRequest = modifyBidRequest(request, account, modifiedImps); + return Result.of( + Collections.singletonList(BidderUtil.defaultRequest(modifiedRequest, endpointUrl, mapper)), + errors); + } + + private ExtImpTeal parseImpExt(Imp imp) { + try { + return mapper.mapper().convertValue(imp.getExt(), TYPE_REFERENCE).getBidder(); + } catch (IllegalArgumentException e) { + throw new PreBidException("Error parsing imp.ext for impression " + imp.getId()); + } + } + + private static void validateImpExt(ExtImpTeal extImpTeal) { + if (StringUtils.isBlank(extImpTeal.getAccount())) { + throw new PreBidException("account parameter failed validation"); + } + + final String placement = extImpTeal.getPlacement(); + if (placement != null && StringUtils.isBlank(placement)) { + throw new PreBidException("placement parameter failed validation"); + } + } + + private static Imp modifyImp(Imp imp, String placement) { + if (placement == null) { + return imp; + } + + final ObjectNode modifiedExt = imp.getExt().deepCopy(); + getOrCreate(getOrCreate(modifiedExt, "prebid"), "storedrequest") + .put("id", placement); + + return imp.toBuilder().ext(modifiedExt).build(); + } + + private static ObjectNode getOrCreate(ObjectNode parent, String field) { + final JsonNode child = parent.get(field); + return child != null && child.isObject() + ? (ObjectNode) child + : parent.putObject(field); + } + + private BidRequest modifyBidRequest(BidRequest request, String account, List modifiedImps) { + final ExtRequest ext = ObjectUtils.defaultIfNull(request.getExt(), ExtRequest.empty()); + ext.addProperty("bids", mapper.mapper().createObjectNode().put("pbs", 1)); + + return request.toBuilder() + .site(modifySite(request.getSite(), account)) + .app(modifyApp(request.getApp(), account)) + .imp(modifiedImps) + .ext(ext) + .build(); + } + + private static Site modifySite(Site site, String account) { + return site != null + ? site.toBuilder() + .publisher(modifyPublisher(site.getPublisher(), account)) + .build() + : null; + } + + private static App modifyApp(App app, String account) { + return app != null + ? app.toBuilder() + .publisher(modifyPublisher(app.getPublisher(), account)) + .build() + : null; + } + + private static Publisher modifyPublisher(Publisher publisher, String account) { + return Optional.ofNullable(publisher) + .map(Publisher::toBuilder) + .orElseGet(Publisher::builder) + .id(account) + .build(); + } + + @Override + public final Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { + try { + final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); + return Result.withValues(extractBids(httpCall.getRequest().getPayload(), bidResponse)); + } catch (DecodeException e) { + return Result.withError(BidderError.badServerResponse(e.getMessage())); + } + } + + private static List extractBids(BidRequest bidRequest, BidResponse bidResponse) { + if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { + return Collections.emptyList(); + } + return bidsFromResponse(bidRequest, bidResponse); + } + + private static List bidsFromResponse(BidRequest bidRequest, BidResponse bidResponse) { + return bidResponse.getSeatbid().stream() + .filter(Objects::nonNull) + .map(SeatBid::getBid) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .filter(Objects::nonNull) + .map(bid -> BidderBid.of(bid, getBidType(bid.getImpid(), bidRequest.getImp()), bidResponse.getCur())) + .toList(); + } + + private static BidType getBidType(String impId, List imps) { + for (Imp imp : imps) { + if (imp.getId().equals(impId)) { + if (imp.getBanner() != null) { + return BidType.banner; + } else if (imp.getVideo() != null) { + return BidType.video; + } else if (imp.getAudio() != null) { + return BidType.audio; + } else if (imp.getXNative() != null) { + return BidType.xNative; + } + } + } + return BidType.banner; + } +} diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/teal/ExtImpTeal.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/teal/ExtImpTeal.java new file mode 100644 index 00000000000..e52ec990e84 --- /dev/null +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/teal/ExtImpTeal.java @@ -0,0 +1,11 @@ +package org.prebid.server.proto.openrtb.ext.request.teal; + +import lombok.Value; + +@Value(staticConstructor = "of") +public class ExtImpTeal { + + String account; + + String placement; +} diff --git a/src/main/java/org/prebid/server/spring/config/bidder/TealConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/TealConfiguration.java new file mode 100644 index 00000000000..cbbff678a7e --- /dev/null +++ b/src/main/java/org/prebid/server/spring/config/bidder/TealConfiguration.java @@ -0,0 +1,42 @@ +package org.prebid.server.spring.config.bidder; + +import org.prebid.server.bidder.BidderDeps; +import org.prebid.server.bidder.teal.TealBidder; +import org.prebid.server.json.JacksonMapper; +import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties; +import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler; +import org.prebid.server.spring.config.bidder.util.UsersyncerCreator; +import org.prebid.server.spring.env.YamlPropertySourceFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +import jakarta.validation.constraints.NotBlank; + +@Configuration +@PropertySource(value = "classpath:/bidder-config/teal.yaml", factory = YamlPropertySourceFactory.class) +public class TealConfiguration { + + private static final String BIDDER_NAME = "teal"; + + @Bean("tealConfigurationProperties") + @ConfigurationProperties("adapters.teal") + BidderConfigurationProperties configurationProperties() { + return new BidderConfigurationProperties(); + } + + @Bean + BidderDeps tealBidderDeps(BidderConfigurationProperties tealConfigurationProperties, + @NotBlank @Value("${external-url}") String externalUrl, + JacksonMapper mapper) { + + return BidderDepsAssembler.forBidder(BIDDER_NAME) + .withConfig(tealConfigurationProperties) + .usersyncerCreator(UsersyncerCreator.create(externalUrl)) + .bidderCreator(config -> new TealBidder(config.getEndpoint(), mapper)) + .assemble(); + } + +} diff --git a/src/main/resources/bidder-config/teal.yaml b/src/main/resources/bidder-config/teal.yaml new file mode 100644 index 00000000000..00d1a6d48fc --- /dev/null +++ b/src/main/resources/bidder-config/teal.yaml @@ -0,0 +1,30 @@ +adapters: + teal: + ortb-version: "2.6" + endpoint: https://a.bids.ws/openrtb2/auction + modifying-vast-xml-allowed: true + endpoint-compression: gzip + geoscope: + - global + aliases: + tealplus: + enabled: false + ortb: + multiformat-supported: true + meta-info: + maintainer-email: prebid@teal.works + app-media-types: + - banner + - video + - native + site-media-types: + - banner + - video + - native + supported-vendors: + vendor-id: 1378 + usersync: + cookie-family-name: teal + iframe: + url: https://bids.ws/load-pbs.html?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redirect_url={{redirect_url}} + support-cors: false diff --git a/src/main/resources/static/bidder-params/teal.json b/src/main/resources/static/bidder-params/teal.json new file mode 100644 index 00000000000..52e073c3af4 --- /dev/null +++ b/src/main/resources/static/bidder-params/teal.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Teal Adapter Params", + "description": "A schema which validates params accepted by the Teal adapter", + "type": "object", + "properties": { + "account": { + "type": "string", + "description": "Account ID" + }, + "placement": { + "type": "string", + "description": "Placement ID or name (optional)" + } + }, + "required": [ + "account" + ] +} diff --git a/src/test/java/org/prebid/server/bidder/teal/TealBidderTest.java b/src/test/java/org/prebid/server/bidder/teal/TealBidderTest.java new file mode 100644 index 00000000000..200902919df --- /dev/null +++ b/src/test/java/org/prebid/server/bidder/teal/TealBidderTest.java @@ -0,0 +1,273 @@ +package org.prebid.server.bidder.teal; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.iab.openrtb.request.Banner; +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Imp; +import com.iab.openrtb.request.Native; +import com.iab.openrtb.request.Publisher; +import com.iab.openrtb.request.Site; +import com.iab.openrtb.request.Video; +import com.iab.openrtb.response.Bid; +import com.iab.openrtb.response.BidResponse; +import com.iab.openrtb.response.SeatBid; +import org.junit.jupiter.api.Test; +import org.prebid.server.VertxTest; +import org.prebid.server.bidder.model.BidderBid; +import org.prebid.server.bidder.model.BidderCall; +import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.HttpRequest; +import org.prebid.server.bidder.model.HttpResponse; +import org.prebid.server.bidder.model.Result; +import org.prebid.server.proto.openrtb.ext.ExtPrebid; +import org.prebid.server.proto.openrtb.ext.request.teal.ExtImpTeal; +import org.prebid.server.proto.openrtb.ext.response.BidType; + +import java.math.BigDecimal; +import java.util.List; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +public class TealBidderTest extends VertxTest { + + private static final String ENDPOINT_URL = "https://test.endpoint.com/"; + + private final TealBidder target = new TealBidder(ENDPOINT_URL, jacksonMapper); + + @Test + public void creationShouldFailOnInvalidEndpointUrl() { + assertThatIllegalArgumentException().isThrownBy(() -> new TealBidder("invalid_url", jacksonMapper)); + } + + @Test + public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() { + // given + final BidRequest bidRequest = givenBidRequest( + imp -> imp.ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(1) + .allSatisfy(error -> { + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_input); + assertThat(error.getMessage()).startsWith("Error parsing imp.ext for impression impId"); + }); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldReturnErrorIfAccountParamFailsValidation() { + // given + final BidRequest bidRequest = givenBidRequest( + imp -> imp.id("imp1").ext(givenImpExt("", "placement"))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(1) + .allSatisfy(error -> { + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_input); + assertThat(error.getMessage()).startsWith("account parameter failed validation"); + }); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldReturnErrorIfPlacementParamFailsValidation() { + // given + final BidRequest bidRequest = givenBidRequest( + imp -> imp.id("imp1").ext(givenImpExt("account", ""))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).hasSize(1) + .allSatisfy(error -> { + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_input); + assertThat(error.getMessage()).startsWith("placement parameter failed validation"); + }); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeHttpRequestsShouldMapParametersCorrectly() { + // given + final BidRequest bidRequest = givenBidRequest( + Site.builder().publisher(Publisher.builder().domain("mydomain.com").build()).build(), + imp -> imp.id("imp1").ext(givenImpExt("account", "placement1")), + imp -> imp.id("imp2").ext(givenImpExt("account", null))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + //then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getSite) + .extracting(Site::getPublisher) + .extracting(Publisher::getId) + .containsExactly("account"); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getExt) + .element(0) + .extracting(ext -> ext.get("prebid")) + .extracting(prebid -> prebid.get("storedrequest")) + .extracting(storedRequest -> storedRequest.get("id")) + .extracting(JsonNode::textValue) + .isEqualTo("placement1"); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getExt) + .element(1) + .extracting(ext -> ext.get("prebid")) + .isNull(); + } + + @Test + public void makeHttpRequestsShouldAddExtBidsPBSFlag() { + // given + final BidRequest bidRequest = givenBidRequest( + imp -> imp.id("imp1").ext(givenImpExt("account", "placement1"))); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + //then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()) + .extracting(HttpRequest::getPayload) + .extracting(BidRequest::getExt) + .extracting(ext -> ext.getProperty("bids")) + .extracting(bids -> bids.get("pbs").toString()) + .containsExactly("1"); + } + + @Test + public void makeBidsShouldReturnErrorWhenResponseBodyCouldNotBeParsed() { + // given + final BidderCall httpCall = BidderCall.succeededHttp( + HttpRequest.builder().build(), + HttpResponse.of(200, null, "invalid_json"), + null); + + // when + final Result> result = target.makeBids(httpCall, null); + + // then + assertThat(result.getErrors()).hasSize(1) + .allSatisfy(error -> { + assertThat(error.getType()).isEqualTo(BidderError.Type.bad_server_response); + assertThat(error.getMessage()).startsWith("Failed to decode: Unrecognized token 'invalid_json'"); + }); + assertThat(result.getValue()).isEmpty(); + } + + @Test + public void makeBidsShouldReturnBannerBid() throws JsonProcessingException { + // given + final Bid responseBid = givenBid("imp1", 1, 1); + final BidRequest bidRequest = givenBidRequest(imp -> imp.id("imp1").banner(Banner.builder().id("id").build())); + final BidderCall httpCall = givenHttpCall(bidRequest, singletonList(responseBid)); + + // when + final Result> result = target.makeBids(httpCall, bidRequest); + + // then + final Bid expectedBid = givenBid("imp1", 1, 1); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsExactly(BidderBid.of(expectedBid, BidType.banner, "USD")); + } + + @Test + public void makeBidsShouldReturnVideoBid() throws JsonProcessingException { + // given + final Bid responseBid = givenBid("imp1", 2, 1); + final BidRequest bidRequest = givenBidRequest(imp -> imp.id("imp1").video(Video.builder().pos(1).build())); + final BidderCall httpCall = givenHttpCall(bidRequest, singletonList(responseBid)); + + // when + final Result> result = target.makeBids(httpCall, bidRequest); + + // then + final Bid expectedBid = givenBid("imp1", 2, 1); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsExactly(BidderBid.of(expectedBid, BidType.video, "USD")); + } + + @Test + public void makeBidsShouldReturnNativeBid() throws JsonProcessingException { + // given + final Bid responseBid = givenBid("imp1", 4, 1); + final BidRequest bidRequest = givenBidRequest(imp -> imp.id("imp1").xNative(Native.builder().ver("1").build())); + final BidderCall httpCall = givenHttpCall(bidRequest, singletonList(responseBid)); + + // when + final Result> result = target.makeBids(httpCall, bidRequest); + + // then + final Bid expectedBid = givenBid("imp1", 4, 1); + + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).containsExactly(BidderBid.of(expectedBid, BidType.xNative, "USD")); + } + + private static BidRequest givenBidRequest(UnaryOperator impCustomizer) { + return givenBidRequest(null, impCustomizer); + } + + private static BidRequest givenBidRequest(Site site, UnaryOperator... impCustomizers) { + final List imps = Stream.of(impCustomizers) + .map(customizer -> customizer.apply(Imp.builder().id("impId")).build()) + .toList(); + return BidRequest.builder().imp(imps).site(site).build(); + } + + private static ObjectNode givenImpExt(String account, String placement) { + return mapper.valueToTree(ExtPrebid.of(null, ExtImpTeal.of(account, placement))); + } + + private static Bid givenBid(String id, Integer mType, int price) { + return Bid.builder() + .id("teal-" + id) + .impid(id) + .mtype(mType) + .price(BigDecimal.valueOf(price)) + .adm("adm") + .w(300) + .h(250) + .adomain(singletonList("adomain.com")) + .ext(mapper.createObjectNode()) + .build(); + } + + private static BidResponse givenBidResponse(List bids) { + return BidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder().bid(bids).build())) + .build(); + } + + private static BidderCall givenHttpCall(BidRequest bidRequest, List bids) + throws JsonProcessingException { + return BidderCall.succeededHttp( + HttpRequest.builder().payload(bidRequest).build(), + HttpResponse.of(200, null, mapper.writeValueAsString(givenBidResponse(bids))), + null); + } +} diff --git a/src/test/java/org/prebid/server/it/TealTest.java b/src/test/java/org/prebid/server/it/TealTest.java new file mode 100644 index 00000000000..bad01a8fb69 --- /dev/null +++ b/src/test/java/org/prebid/server/it/TealTest.java @@ -0,0 +1,32 @@ +package org.prebid.server.it; + +import io.restassured.response.Response; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.prebid.server.model.Endpoint; + +import java.io.IOException; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static java.util.Collections.singletonList; + +public class TealTest extends IntegrationTest { + + @Test + public void openrtb2AuctionShouldRespondWithBidsFromTeal() throws IOException, JSONException { + // given + WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/teal-exchange")) + .withRequestBody(equalToJson(jsonFrom("openrtb2/teal/test-teal-bid-request.json"))) + .willReturn(aResponse().withBody(jsonFrom("openrtb2/teal/test-teal-bid-response.json")))); + + // when + final Response response = responseFor("openrtb2/teal/test-auction-teal-request.json", + Endpoint.openrtb2_auction); + + // then + assertJsonEquals("openrtb2/teal/test-auction-teal-response.json", response, singletonList("teal")); + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/teal/test-auction-teal-request.json b/src/test/resources/org/prebid/server/it/openrtb2/teal/test-auction-teal-request.json new file mode 100644 index 00000000000..af84f5cf1b7 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/teal/test-auction-teal-request.json @@ -0,0 +1,44 @@ +{ + "id": "test-request-banner", + "imp": [ + { + "id": "test-imp-banner", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "teal": { + "account": "test-account", + "placement": "test-placement300x250" + } + } + } + ], + "site": { + "id": "demo-site", + "domain": "example.com", + "page": "https://example.com/demo", + "publisher": { + "id": "demo-publisher" + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + "ip": "192.0.2.1", + "language": "en", + "dnt": 0 + }, + "user": { + "id": "demo-user" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/teal/test-auction-teal-response.json b/src/test/resources/org/prebid/server/it/openrtb2/teal/test-auction-teal-response.json new file mode 100644 index 00000000000..e183f1a0330 --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/teal/test-auction-teal-response.json @@ -0,0 +1,41 @@ +{ + "id": "test-request-banner", + "seatbid": [ + { + "seat": "teal", + "bid": [ + { + "id": "test-imp-banner", + "impid": "test-imp-banner", + "price": 2.50, + "adm": "
Teal Demo Ad
", + "w": 300, + "h": 250, + "crid": "demo-creative-123", + "exp": 300, + "ext": { + "origbidcpm": 2.50, + "origbidcur": "USD", + "prebid": { + "type": "banner", + "meta": { + "adaptercode": "teal" + } + } + } + } + ], + "group": 0 + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "teal": "{{ teal.response_time_ms }}" + }, + "prebid": { + "auctiontimestamp": 0 + }, + "tmaxrequest": 5000 + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/teal/test-teal-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/teal/test-teal-bid-request.json new file mode 100644 index 00000000000..c26cb49987d --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/teal/test-teal-bid-request.json @@ -0,0 +1,77 @@ +{ + "id": "test-request-banner", + "imp": [ + { + "id": "test-imp-banner", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "secure": 1, + "ext": { + "tid": "${json-unit.any-string}", + "bidder": { + "account": "test-account", + "placement": "test-placement300x250" + }, + "prebid": { + "storedrequest": { + "id": "test-placement300x250" + } + } + } + } + ], + "site": { + "id": "demo-site", + "domain": "example.com", + "page": "https://example.com/demo", + "publisher": { + "id": "test-account", + "domain": "example.com" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "dnt": 0, + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + "ip": "192.0.2.1", + "language": "en" + }, + "user": { + "id": "demo-user" + }, + "at": 1, + "tmax": "${json-unit.any-number}", + "cur": [ + "USD" + ], + "source": { + "tid": "${json-unit.any-string}" + }, + "regs": { + "gdpr": 0 + }, + "ext": { + "prebid": { + "channel": { + "name": "web" + }, + "server": { + "externalurl": "http://localhost:8080", + "gvlid": 1, + "datacenter": "local", + "endpoint": "/openrtb2/auction" + } + }, + "bids": { + "pbs": 1 + } + } +} diff --git a/src/test/resources/org/prebid/server/it/openrtb2/teal/test-teal-bid-response.json b/src/test/resources/org/prebid/server/it/openrtb2/teal/test-teal-bid-response.json new file mode 100644 index 00000000000..2c5a13641ff --- /dev/null +++ b/src/test/resources/org/prebid/server/it/openrtb2/teal/test-teal-bid-response.json @@ -0,0 +1,29 @@ +{ + "id": "test-request-banner", + "seatbid": [ + { + "bid": [ + { + "id": "test-imp-banner", + "impid": "test-imp-banner", + "price": 2.50, + "adm": "
Teal Demo Ad
", + "w": 300, + "h": 250, + "crid": "demo-creative-123", + "exp": 300, + "ext": { + "origbidcpm": 2.50, + "origbidcur": "USD", + "prebid": { + "type": "banner", + "meta": { + "adaptercode": "teal" + } + } + } + } + ] + } + ] +} diff --git a/src/test/resources/org/prebid/server/it/test-application.properties b/src/test/resources/org/prebid/server/it/test-application.properties index 439ef436125..8bbbef8c2e4 100644 --- a/src/test/resources/org/prebid/server/it/test-application.properties +++ b/src/test/resources/org/prebid/server/it/test-application.properties @@ -571,6 +571,8 @@ adapters.tappx.enabled=true adapters.tappx.endpoint=http://localhost:8090/tappx-exchange adapters.teads.enabled=true adapters.teads.endpoint=http://localhost:8090/teads-exchange +adapters.teal.enabled=true +adapters.teal.endpoint=http://localhost:8090/teal-exchange adapters.telaria.enabled=true adapters.telaria.endpoint=http://localhost:8090/telaria-exchange/ adapters.teqblaze.enabled=true From 8f912785c97517826ba029fed6fde90c01fdd64d Mon Sep 17 00:00:00 2001 From: Ilya Kazovsky Date: Thu, 5 Feb 2026 15:09:22 +0100 Subject: [PATCH 33/38] Module update: LI EID permissions (#4315) --- .../LiveIntentOmniChannelProperties.java | 4 +- ...elIdentityProcessedAuctionRequestHook.java | 69 +++++---- ...entityProcessedAuctionRequestHookTest.java | 132 +++++++++++++++--- 3 files changed, 156 insertions(+), 49 deletions(-) diff --git a/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/config/LiveIntentOmniChannelProperties.java b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/config/LiveIntentOmniChannelProperties.java index 01d37eabda0..383aacea3a9 100644 --- a/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/config/LiveIntentOmniChannelProperties.java +++ b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/config/LiveIntentOmniChannelProperties.java @@ -2,7 +2,7 @@ import lombok.Data; -import java.util.List; +import java.util.Set; @Data public final class LiveIntentOmniChannelProperties { @@ -15,5 +15,5 @@ public final class LiveIntentOmniChannelProperties { float treatmentRate; - List targetBidders; + Set targetBidders; } diff --git a/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/hooks/LiveIntentOmniChannelIdentityProcessedAuctionRequestHook.java b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/hooks/LiveIntentOmniChannelIdentityProcessedAuctionRequestHook.java index 32b608ce21c..3afdb146018 100644 --- a/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/hooks/LiveIntentOmniChannelIdentityProcessedAuctionRequestHook.java +++ b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/hooks/LiveIntentOmniChannelIdentityProcessedAuctionRequestHook.java @@ -9,6 +9,7 @@ import io.vertx.core.MultiMap; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.ListUtils; +import org.apache.commons.collections4.SetUtils; import org.prebid.server.activity.Activity; import org.prebid.server.activity.ComponentType; import org.prebid.server.activity.infrastructure.ActivityInfrastructure; @@ -40,18 +41,15 @@ import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidDataEidPermissions; import org.prebid.server.util.HttpUtil; import org.prebid.server.util.ListUtil; -import org.prebid.server.util.StreamUtil; import org.prebid.server.vertx.httpclient.HttpClient; import org.prebid.server.vertx.httpclient.model.HttpClientResponse; -import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; -import java.util.stream.Stream; public class LiveIntentOmniChannelIdentityProcessedAuctionRequestHook implements ProcessedAuctionRequestHook { @@ -65,7 +63,7 @@ public class LiveIntentOmniChannelIdentityProcessedAuctionRequestHook implements private final HttpClient httpClient; private final UserFpdActivityMask userFpdActivityMask; private final double logSamplingRate; - private final List targetBidders; + private final Set targetBidders; public LiveIntentOmniChannelIdentityProcessedAuctionRequestHook(LiveIntentOmniChannelProperties config, UserFpdActivityMask userFpdActivityMask, @@ -79,7 +77,7 @@ public LiveIntentOmniChannelIdentityProcessedAuctionRequestHook(LiveIntentOmniCh this.httpClient = Objects.requireNonNull(httpClient); this.logSamplingRate = logSamplingRate; this.userFpdActivityMask = Objects.requireNonNull(userFpdActivityMask); - this.targetBidders = ListUtils.emptyIfNull(config.getTargetBidders()); + this.targetBidders = SetUtils.emptyIfNull(config.getTargetBidders()); } @Override @@ -202,7 +200,7 @@ private AuctionRequestPayload updatedPayload(AuctionRequestPayload requestPayloa } private BidRequest updateAllowedBidders(BidRequest bidRequest, List resolvedEids) { - if (targetBidders.isEmpty()) { + if (CollectionUtils.isEmpty(targetBidders) || CollectionUtils.isEmpty(resolvedEids)) { return bidRequest; } @@ -210,6 +208,14 @@ private BidRequest updateAllowedBidders(BidRequest bidRequest, List resolve final ExtRequestPrebid extPrebid = ext != null ? ext.getPrebid() : null; final ExtRequestPrebidData extPrebidData = extPrebid != null ? extPrebid.getData() : null; + final List existingPerms = extPrebidData != null + ? extPrebidData.getEidPermissions() + : null; + + if (CollectionUtils.isEmpty(existingPerms)) { + return bidRequest; + } + final ExtRequestPrebid updatedExtPrebid = Optional.ofNullable(extPrebid) .map(ExtRequestPrebid::toBuilder) .orElseGet(ExtRequestPrebid::builder) @@ -225,35 +231,38 @@ private BidRequest updateAllowedBidders(BidRequest bidRequest, List resolve } private ExtRequestPrebidData updatePrebidData(ExtRequestPrebidData extPrebidData, List resolvedEids) { - final List prebidDataBidders = extPrebidData != null ? extPrebidData.getBidders() : null; - final List updatedPrebidDataBidders = prebidDataBidders != null - ? (List) CollectionUtils.union(targetBidders, prebidDataBidders) - : targetBidders; - - final Set resolvedSources = resolvedEids.stream().map(Eid::getSource).collect(Collectors.toSet()); - - final List initialPermissions = Optional.ofNullable(extPrebidData) - .map(ExtRequestPrebidData::getEidPermissions) - .orElse(Collections.emptyList()); - final List updatedPermissions = Stream.concat( - initialPermissions.stream() - .map(permission -> updateEidPermission(permission, resolvedSources)), - resolvedSources.stream() - .map(source -> ExtRequestPrebidDataEidPermissions.of(source, targetBidders))) - .filter(StreamUtil.distinctBy(ExtRequestPrebidDataEidPermissions::getSource)) + final List originalBidders = extPrebidData != null ? extPrebidData.getBidders() : null; + + final Set resolvedSources = resolvedEids.stream() + .map(Eid::getSource) + .collect(Collectors.toSet()); + + final List updatedPermissions = extPrebidData.getEidPermissions().stream() + .map(permission -> restrictEidPermission(permission, resolvedSources)) + .filter(Objects::nonNull) .toList(); - return ExtRequestPrebidData.of(updatedPrebidDataBidders, updatedPermissions); + return ExtRequestPrebidData.of(originalBidders, updatedPermissions); } - private ExtRequestPrebidDataEidPermissions updateEidPermission(ExtRequestPrebidDataEidPermissions permission, - Set resolvedSources) { + private ExtRequestPrebidDataEidPermissions restrictEidPermission(ExtRequestPrebidDataEidPermissions permission, + Set resolvedSources) { - return resolvedSources.contains(permission.getSource()) - ? ExtRequestPrebidDataEidPermissions.of( - permission.getSource(), - (List) CollectionUtils.union(permission.getBidders(), targetBidders)) - : permission; + if (!resolvedSources.contains(permission.getSource())) { + return permission; + } + + final List finalBidders = ListUtils.emptyIfNull(permission.getBidders()).stream() + .filter(targetBidders::contains) + .toList(); + + return CollectionUtils.isEmpty(finalBidders) + ? null + : ExtRequestPrebidDataEidPermissions + .builder() + .bidders(finalBidders) + .source(permission.getSource()) + .build(); } @Override diff --git a/extra/modules/live-intent-omni-channel-identity/src/test/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/LiveIntentOmniChannelIdentityProcessedAuctionRequestHookTest.java b/extra/modules/live-intent-omni-channel-identity/src/test/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/LiveIntentOmniChannelIdentityProcessedAuctionRequestHookTest.java index 7b05ec6072d..4dfaa8c8df4 100644 --- a/extra/modules/live-intent-omni-channel-identity/src/test/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/LiveIntentOmniChannelIdentityProcessedAuctionRequestHookTest.java +++ b/extra/modules/live-intent-omni-channel-identity/src/test/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/LiveIntentOmniChannelIdentityProcessedAuctionRequestHookTest.java @@ -36,6 +36,7 @@ import org.prebid.server.vertx.httpclient.model.HttpClientResponse; import java.util.List; +import java.util.Set; import java.util.concurrent.TimeoutException; import static java.util.Collections.singletonList; @@ -74,11 +75,11 @@ public class LiveIntentOmniChannelIdentityProcessedAuctionRequestHookTest { private LiveIntentOmniChannelIdentityProcessedAuctionRequestHook target; - private List configuredBidders; + private Set configuredBidders; @BeforeEach public void setUp() { - configuredBidders = List.of("bidder1", "bidder2"); + configuredBidders = Set.of("bidder1", "bidder2"); given(properties.getRequestTimeoutMs()).willReturn(5L); given(properties.getIdentityResolutionEndpoint()).willReturn("https://test.com/idres"); given(properties.getAuthToken()).willReturn("auth_token"); @@ -375,14 +376,38 @@ public void callShouldReturnFailureWhenRequestingEidsIsFailed() { } @Test - public void biddersConfiguredRestrictionShouldBeRespected() { + public void shouldRestrictExistingEidPermissionsByIntersectionAndKeepGlobalBiddersUnchanged() { + // given final Uid givenUid = Uid.builder().id("id1").atype(2).build(); final Eid givenEid = Eid.builder().source("some.source.com").uids(singletonList(givenUid)).build(); final User givenUser = User.builder().eids(singletonList(givenEid)).build(); - final BidRequest givenBidRequest = BidRequest.builder().id("request").user(givenUser).build(); - final ExtRequestPrebidData expectedData = ExtRequestPrebidData.of(configuredBidders, List.of( - ExtRequestPrebidDataEidPermissions.of("liveintent.com", configuredBidders))); + final ExtRequestPrebidDataEidPermissions otherBidder = ExtRequestPrebidDataEidPermissions.builder() + .source("some.other-source.com") + .bidders(singletonList("bidderY")) + .build(); + + final ExtRequestPrebidDataEidPermissions liBidder2 = ExtRequestPrebidDataEidPermissions.builder() + .source("liveintent.com") + .bidders(singletonList("bidder2")) + .build(); + final ExtRequestPrebidDataEidPermissions liBidder23 = ExtRequestPrebidDataEidPermissions.builder() + .source("liveintent.com") + .bidders(List.of("bidder2", "bidder3")) + .build(); + + final ExtRequestPrebidData givenData = ExtRequestPrebidData.of(singletonList("bidderX"), + List.of(otherBidder, liBidder23)); + + final BidRequest givenBidRequest = BidRequest.builder() + .id("request") + .user(givenUser) + .ext(ExtRequest.of(ExtRequestPrebid.builder().data(givenData).build())) + .build(); + + final ExtRequestPrebidData expectedData = ExtRequestPrebidData.of( + List.of("bidderX"), + List.of(otherBidder, liBidder2)); final Eid expectedEid = Eid.builder().source("liveintent.com").build(); @@ -418,23 +443,31 @@ public void biddersConfiguredRestrictionShouldBeRespected() { } @Test - public void biddersConfiguredRestrictionShouldBeMergedWithProvided() { + public void shouldNotAddNewEidPermissionsOrModifyGlobalBiddersWhenSourceNotPresent() { // given final Uid givenUid = Uid.builder().id("id1").atype(2).build(); final Eid givenEid = Eid.builder().source("some.source.com").uids(singletonList(givenUid)).build(); final User givenUser = User.builder().eids(singletonList(givenEid)).build(); - final BidRequest givenBidRequest = BidRequest.builder().id("request").user(givenUser).ext(ExtRequest.of( - ExtRequestPrebid.builder().data(ExtRequestPrebidData.of(List.of("bidder3"), List.of( - ExtRequestPrebidDataEidPermissions.of("some.other-source.com", List.of("bidder3")), - ExtRequestPrebidDataEidPermissions.of("some.source.com", List.of("bidder3")))) - ).build())).build(); + final ExtRequestPrebidDataEidPermissions bidder1 = ExtRequestPrebidDataEidPermissions.builder() + .source("some.other-source.com") + .bidders(singletonList("bidder3")) + .build(); + final ExtRequestPrebidDataEidPermissions bidder2 = ExtRequestPrebidDataEidPermissions.builder() + .source("some.source.com") + .bidders(singletonList("bidder3")) + .build(); - final List expectedBidders = List.of("bidder3", "bidder2", "bidder1"); + final List bidders = List.of(bidder1, bidder2); - final ExtRequestPrebidData expectedData = ExtRequestPrebidData.of(expectedBidders, List.of( - ExtRequestPrebidDataEidPermissions.of("some.other-source.com", List.of("bidder3")), - ExtRequestPrebidDataEidPermissions.of("some.source.com", List.of("bidder3")), - ExtRequestPrebidDataEidPermissions.of("liveintent.com", configuredBidders))); + final BidRequest givenBidRequest = BidRequest.builder() + .id("request") + .user(givenUser) + .ext(ExtRequest.of(ExtRequestPrebid.builder() + .data(ExtRequestPrebidData.of(singletonList("bidder3"), bidders)) + .build())) + .build(); + + final ExtRequestPrebidData expectedData = ExtRequestPrebidData.of(List.of("bidder3"), bidders); final Eid expectedEid = Eid.builder().source("liveintent.com").build(); @@ -468,4 +501,69 @@ public void biddersConfiguredRestrictionShouldBeMergedWithProvided() { eq(MAPPER.encodeToString(givenBidRequest)), eq(5L)); } + + @Test + public void shouldRemovePermissionWhenIntersectionIsEmpty() { + // given + final Uid givenUid = Uid.builder().id("id1").atype(2).build(); + final Eid givenEid = Eid.builder().source("some.source.com").uids(singletonList(givenUid)).build(); + final User givenUser = User.builder().eids(singletonList(givenEid)).build(); + + final ExtRequestPrebidData givenData = ExtRequestPrebidData.of( + List.of("bidderGlobal"), + List.of( + ExtRequestPrebidDataEidPermissions.builder() + .source("liveintent.com") + .bidders(singletonList("not-allowed")) + .build(), + ExtRequestPrebidDataEidPermissions.builder() + .source("keep.com") + .bidders(singletonList("bidderGlobal")) + .build())); + + final BidRequest givenBidRequest = BidRequest.builder() + .id("request") + .user(givenUser) + .ext(ExtRequest.of(ExtRequestPrebid.builder().data(givenData).build())) + .build(); + + final Eid expectedEid = Eid.builder().source("liveintent.com").build(); + final String responseBody = MAPPER.encodeToString(IdResResponse.of(List.of(expectedEid))); + given(httpClient.post(any(), any(), any(), anyLong())) + .willReturn(Future.succeededFuture(HttpClientResponse.of(200, null, responseBody))); + + given(auctionInvocationContext.auctionContext()).willReturn(auctionContext); + given(auctionContext.getActivityInfrastructure()).willReturn(activityInfrastructure); + given(activityInfrastructure.isAllowed(any(), any())).willReturn(true); + given(userFpdActivityMask.maskUser(any(), eq(false), eq(false))) + .willAnswer(invocation -> invocation.getArgument(0)); + given(userFpdActivityMask.maskDevice(any(), eq(false), eq(false))) + .willAnswer(invocation -> invocation.getArgument(0)); + + // when + final InvocationResult result = + target.call(AuctionRequestPayloadImpl.of(givenBidRequest), auctionInvocationContext).result(); + + // then + final ExtRequestPrebidData expectedData = ExtRequestPrebidData.of( + List.of("bidderGlobal"), + List.of(ExtRequestPrebidDataEidPermissions.builder() + .source("keep.com") + .bidders(singletonList("bidderGlobal")) + .build())); + + assertThat(result.status()).isEqualTo(InvocationStatus.success); + assertThat(result.payloadUpdate().apply(AuctionRequestPayloadImpl.of(givenBidRequest))) + .extracting(AuctionRequestPayload::bidRequest) + .extracting(BidRequest::getExt) + .extracting(ExtRequest::getPrebid) + .extracting(ExtRequestPrebid::getData) + .isEqualTo(expectedData); + + verify(httpClient).post( + eq("https://test.com/idres"), + argThat(headers -> headers.contains("Authorization", "Bearer auth_token", true)), + eq(MAPPER.encodeToString(givenBidRequest)), + eq(5L)); + } } From 702bf55052414c26404416aab4bb6017929fd1fa Mon Sep 17 00:00:00 2001 From: Oleksandr Zhevedenko <720803+Net-burst@users.noreply.github.com> Date: Fri, 6 Feb 2026 12:51:41 -0500 Subject: [PATCH 34/38] Prebid Server prepare release 3.39.0 --- extra/bundle/pom.xml | 2 +- extra/modules/confiant-ad-quality/pom.xml | 2 +- extra/modules/fiftyone-devicedetection/pom.xml | 2 +- extra/modules/greenbids-real-time-data/pom.xml | 2 +- extra/modules/live-intent-omni-channel-identity/pom.xml | 2 +- extra/modules/optable-targeting/pom.xml | 2 +- extra/modules/ortb2-blocking/pom.xml | 2 +- extra/modules/pb-request-correction/pom.xml | 2 +- extra/modules/pb-response-correction/pom.xml | 2 +- extra/modules/pb-richmedia-filter/pom.xml | 2 +- extra/modules/pb-rule-engine/pom.xml | 2 +- extra/modules/pom.xml | 2 +- extra/modules/wurfl-devicedetection/pom.xml | 2 +- extra/pom.xml | 4 ++-- pom.xml | 2 +- 15 files changed, 16 insertions(+), 16 deletions(-) diff --git a/extra/bundle/pom.xml b/extra/bundle/pom.xml index 5d44255ca2d..2575bc12d7f 100644 --- a/extra/bundle/pom.xml +++ b/extra/bundle/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.39.0-SNAPSHOT + 3.39.0 ../../extra/pom.xml diff --git a/extra/modules/confiant-ad-quality/pom.xml b/extra/modules/confiant-ad-quality/pom.xml index 1d86482129b..e1b051cbb63 100644 --- a/extra/modules/confiant-ad-quality/pom.xml +++ b/extra/modules/confiant-ad-quality/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0-SNAPSHOT + 3.39.0 confiant-ad-quality diff --git a/extra/modules/fiftyone-devicedetection/pom.xml b/extra/modules/fiftyone-devicedetection/pom.xml index aafbfa859ac..3a1c721d19d 100644 --- a/extra/modules/fiftyone-devicedetection/pom.xml +++ b/extra/modules/fiftyone-devicedetection/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0-SNAPSHOT + 3.39.0 fiftyone-devicedetection diff --git a/extra/modules/greenbids-real-time-data/pom.xml b/extra/modules/greenbids-real-time-data/pom.xml index 141e4cf087e..d6031d40981 100644 --- a/extra/modules/greenbids-real-time-data/pom.xml +++ b/extra/modules/greenbids-real-time-data/pom.xml @@ -4,7 +4,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0-SNAPSHOT + 3.39.0 greenbids-real-time-data diff --git a/extra/modules/live-intent-omni-channel-identity/pom.xml b/extra/modules/live-intent-omni-channel-identity/pom.xml index c0c6c463354..c6cb348888b 100644 --- a/extra/modules/live-intent-omni-channel-identity/pom.xml +++ b/extra/modules/live-intent-omni-channel-identity/pom.xml @@ -4,7 +4,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0-SNAPSHOT + 3.39.0 live-intent-omni-channel-identity diff --git a/extra/modules/optable-targeting/pom.xml b/extra/modules/optable-targeting/pom.xml index 9e6503995c0..ce6127a0d06 100644 --- a/extra/modules/optable-targeting/pom.xml +++ b/extra/modules/optable-targeting/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0-SNAPSHOT + 3.39.0 optable-targeting diff --git a/extra/modules/ortb2-blocking/pom.xml b/extra/modules/ortb2-blocking/pom.xml index 55107ec2223..d4f4980c810 100644 --- a/extra/modules/ortb2-blocking/pom.xml +++ b/extra/modules/ortb2-blocking/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0-SNAPSHOT + 3.39.0 ortb2-blocking diff --git a/extra/modules/pb-request-correction/pom.xml b/extra/modules/pb-request-correction/pom.xml index 30fe61d6498..59959ad4589 100644 --- a/extra/modules/pb-request-correction/pom.xml +++ b/extra/modules/pb-request-correction/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0-SNAPSHOT + 3.39.0 pb-request-correction diff --git a/extra/modules/pb-response-correction/pom.xml b/extra/modules/pb-response-correction/pom.xml index faf24c6baba..f7135affabe 100644 --- a/extra/modules/pb-response-correction/pom.xml +++ b/extra/modules/pb-response-correction/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0-SNAPSHOT + 3.39.0 pb-response-correction diff --git a/extra/modules/pb-richmedia-filter/pom.xml b/extra/modules/pb-richmedia-filter/pom.xml index e8db972c01e..ab32b13b9e7 100644 --- a/extra/modules/pb-richmedia-filter/pom.xml +++ b/extra/modules/pb-richmedia-filter/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0-SNAPSHOT + 3.39.0 pb-richmedia-filter diff --git a/extra/modules/pb-rule-engine/pom.xml b/extra/modules/pb-rule-engine/pom.xml index 0d6d567a809..9836e984f4d 100644 --- a/extra/modules/pb-rule-engine/pom.xml +++ b/extra/modules/pb-rule-engine/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0-SNAPSHOT + 3.39.0 pb-rule-engine diff --git a/extra/modules/pom.xml b/extra/modules/pom.xml index e18fe0f665e..07596d72216 100644 --- a/extra/modules/pom.xml +++ b/extra/modules/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.39.0-SNAPSHOT + 3.39.0 ../../extra/pom.xml diff --git a/extra/modules/wurfl-devicedetection/pom.xml b/extra/modules/wurfl-devicedetection/pom.xml index 1068dc04cd4..929581a698b 100644 --- a/extra/modules/wurfl-devicedetection/pom.xml +++ b/extra/modules/wurfl-devicedetection/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0-SNAPSHOT + 3.39.0 wurfl-devicedetection diff --git a/extra/pom.xml b/extra/pom.xml index 836df8f4a38..0533b8dd32f 100644 --- a/extra/pom.xml +++ b/extra/pom.xml @@ -4,14 +4,14 @@ org.prebid prebid-server-aggregator - 3.39.0-SNAPSHOT + 3.39.0 pom https://github.com/prebid/prebid-server-java scm:git:git@github.com:prebid/prebid-server-java.git scm:git:git@github.com:prebid/prebid-server-java.git - HEAD + 3.39.0 diff --git a/pom.xml b/pom.xml index b9f4a2eff8b..9a9dead02bd 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.39.0-SNAPSHOT + 3.39.0 extra/pom.xml From 0e8ca155fc6063613ad447aa50c6aa5cb297e5a8 Mon Sep 17 00:00:00 2001 From: Oleksandr Zhevedenko <720803+Net-burst@users.noreply.github.com> Date: Fri, 6 Feb 2026 12:51:41 -0500 Subject: [PATCH 35/38] Prebid Server prepare for next development iteration --- extra/bundle/pom.xml | 2 +- extra/modules/confiant-ad-quality/pom.xml | 2 +- extra/modules/fiftyone-devicedetection/pom.xml | 2 +- extra/modules/greenbids-real-time-data/pom.xml | 2 +- extra/modules/live-intent-omni-channel-identity/pom.xml | 2 +- extra/modules/optable-targeting/pom.xml | 2 +- extra/modules/ortb2-blocking/pom.xml | 2 +- extra/modules/pb-request-correction/pom.xml | 2 +- extra/modules/pb-response-correction/pom.xml | 2 +- extra/modules/pb-richmedia-filter/pom.xml | 2 +- extra/modules/pb-rule-engine/pom.xml | 2 +- extra/modules/pom.xml | 2 +- extra/modules/wurfl-devicedetection/pom.xml | 2 +- extra/pom.xml | 4 ++-- pom.xml | 2 +- 15 files changed, 16 insertions(+), 16 deletions(-) diff --git a/extra/bundle/pom.xml b/extra/bundle/pom.xml index 2575bc12d7f..a210a0919ee 100644 --- a/extra/bundle/pom.xml +++ b/extra/bundle/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.39.0 + 3.40.0-SNAPSHOT ../../extra/pom.xml diff --git a/extra/modules/confiant-ad-quality/pom.xml b/extra/modules/confiant-ad-quality/pom.xml index e1b051cbb63..e2f82de7886 100644 --- a/extra/modules/confiant-ad-quality/pom.xml +++ b/extra/modules/confiant-ad-quality/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0 + 3.40.0-SNAPSHOT confiant-ad-quality diff --git a/extra/modules/fiftyone-devicedetection/pom.xml b/extra/modules/fiftyone-devicedetection/pom.xml index 3a1c721d19d..5c1dbb9ccc0 100644 --- a/extra/modules/fiftyone-devicedetection/pom.xml +++ b/extra/modules/fiftyone-devicedetection/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0 + 3.40.0-SNAPSHOT fiftyone-devicedetection diff --git a/extra/modules/greenbids-real-time-data/pom.xml b/extra/modules/greenbids-real-time-data/pom.xml index d6031d40981..f5ab8eb4b54 100644 --- a/extra/modules/greenbids-real-time-data/pom.xml +++ b/extra/modules/greenbids-real-time-data/pom.xml @@ -4,7 +4,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0 + 3.40.0-SNAPSHOT greenbids-real-time-data diff --git a/extra/modules/live-intent-omni-channel-identity/pom.xml b/extra/modules/live-intent-omni-channel-identity/pom.xml index c6cb348888b..e846ccbdad6 100644 --- a/extra/modules/live-intent-omni-channel-identity/pom.xml +++ b/extra/modules/live-intent-omni-channel-identity/pom.xml @@ -4,7 +4,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0 + 3.40.0-SNAPSHOT live-intent-omni-channel-identity diff --git a/extra/modules/optable-targeting/pom.xml b/extra/modules/optable-targeting/pom.xml index ce6127a0d06..34278806fc8 100644 --- a/extra/modules/optable-targeting/pom.xml +++ b/extra/modules/optable-targeting/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0 + 3.40.0-SNAPSHOT optable-targeting diff --git a/extra/modules/ortb2-blocking/pom.xml b/extra/modules/ortb2-blocking/pom.xml index d4f4980c810..6aa8f9b2792 100644 --- a/extra/modules/ortb2-blocking/pom.xml +++ b/extra/modules/ortb2-blocking/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0 + 3.40.0-SNAPSHOT ortb2-blocking diff --git a/extra/modules/pb-request-correction/pom.xml b/extra/modules/pb-request-correction/pom.xml index 59959ad4589..e1deb0518b8 100644 --- a/extra/modules/pb-request-correction/pom.xml +++ b/extra/modules/pb-request-correction/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0 + 3.40.0-SNAPSHOT pb-request-correction diff --git a/extra/modules/pb-response-correction/pom.xml b/extra/modules/pb-response-correction/pom.xml index f7135affabe..09f08ce6d67 100644 --- a/extra/modules/pb-response-correction/pom.xml +++ b/extra/modules/pb-response-correction/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0 + 3.40.0-SNAPSHOT pb-response-correction diff --git a/extra/modules/pb-richmedia-filter/pom.xml b/extra/modules/pb-richmedia-filter/pom.xml index ab32b13b9e7..735704921f1 100644 --- a/extra/modules/pb-richmedia-filter/pom.xml +++ b/extra/modules/pb-richmedia-filter/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0 + 3.40.0-SNAPSHOT pb-richmedia-filter diff --git a/extra/modules/pb-rule-engine/pom.xml b/extra/modules/pb-rule-engine/pom.xml index 9836e984f4d..3fe12665358 100644 --- a/extra/modules/pb-rule-engine/pom.xml +++ b/extra/modules/pb-rule-engine/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0 + 3.40.0-SNAPSHOT pb-rule-engine diff --git a/extra/modules/pom.xml b/extra/modules/pom.xml index 07596d72216..d55994fd58c 100644 --- a/extra/modules/pom.xml +++ b/extra/modules/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.39.0 + 3.40.0-SNAPSHOT ../../extra/pom.xml diff --git a/extra/modules/wurfl-devicedetection/pom.xml b/extra/modules/wurfl-devicedetection/pom.xml index 929581a698b..dde02bed3ed 100644 --- a/extra/modules/wurfl-devicedetection/pom.xml +++ b/extra/modules/wurfl-devicedetection/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.39.0 + 3.40.0-SNAPSHOT wurfl-devicedetection diff --git a/extra/pom.xml b/extra/pom.xml index 0533b8dd32f..a86c0d18888 100644 --- a/extra/pom.xml +++ b/extra/pom.xml @@ -4,14 +4,14 @@ org.prebid prebid-server-aggregator - 3.39.0 + 3.40.0-SNAPSHOT pom https://github.com/prebid/prebid-server-java scm:git:git@github.com:prebid/prebid-server-java.git scm:git:git@github.com:prebid/prebid-server-java.git - 3.39.0 + HEAD diff --git a/pom.xml b/pom.xml index 9a9dead02bd..73513000b95 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.prebid prebid-server-aggregator - 3.39.0 + 3.40.0-SNAPSHOT extra/pom.xml From b416c7672fdcdfbafaca51084141fd5d1c9a8e09 Mon Sep 17 00:00:00 2001 From: osulzhenko <125548596+osulzhenko@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:30:06 +0200 Subject: [PATCH 36/38] Housekeeping: Bump Spring Boot minor version (#4387) --- extra/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extra/pom.xml b/extra/pom.xml index a86c0d18888..2cb81b6464a 100644 --- a/extra/pom.xml +++ b/extra/pom.xml @@ -33,7 +33,7 @@ 10.17.0 - 3.5.5 + 3.5.10 4.5.20 2.0.1.Final 4.4 From 88f90d7589ffa6a7590698a7e82daccbe6f3e8a6 Mon Sep 17 00:00:00 2001 From: markiian Date: Tue, 24 Feb 2026 16:41:42 +0200 Subject: [PATCH 37/38] Minor update after review --- .../groovy/org/prebid/server/functional/tests/SetUidSpec.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy index 92b457414a4..a4c18c59fc7 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy @@ -69,6 +69,7 @@ class SetUidSpec extends BaseSpec { "adapters.${GRID.value}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, "adapters.${GRID.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, "adapters.${GRID.value}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString(), + "adapters.${GRID.value}.usersync.${USER_SYNC_TYPE.value}.uid-macro" : "", "adapters.${GENERIC}.aliases.${ALIAS}.enabled" : "true", "adapters.${GENERIC}.aliases.${ALIAS}.endpoint" : "$networkServiceContainer.rootUri/auction".toString(), From b5d42e2e73eb3486aca546b84b5a6a000e90777d Mon Sep 17 00:00:00 2001 From: markiian Date: Tue, 24 Feb 2026 18:54:51 +0200 Subject: [PATCH 38/38] Remove empty line --- .../org/prebid/server/functional/tests/SetUidSpec.groovy | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy index a4c18c59fc7..06a4e22a518 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy @@ -83,9 +83,7 @@ class SetUidSpec extends BaseSpec { "adapters.${GENERIC}.aliases.${OPENX_ALIAS}.meta-info.vendor-id" : VENDOR_ID, "adapters.${GENERIC}.aliases.${OPENX_ALIAS}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME, "adapters.${GENERIC}.aliases.${OPENX_ALIAS}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, - "adapters.${GENERIC}.aliases.${OPENX_ALIAS}.usersync.${USER_SYNC_TYPE.value}.support-cors": CORS_SUPPORT.toString() - - ] + "adapters.${GENERIC}.aliases.${OPENX_ALIAS}.usersync.${USER_SYNC_TYPE.value}.support-cors": CORS_SUPPORT.toString()] @Shared PrebidServerService singleCookiesPbsService = pbsServiceFactory.getService(PBS_CONFIG + GENERIC_ALIAS_CONFIG)