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 @@