From 41c0e0171a2d66fe9e1cee2a67e5cec1e8459bff Mon Sep 17 00:00:00 2001
From: Oleksandr Zhevedenko <720803+Net-burst@users.noreply.github.com>
Date: Fri, 26 Sep 2025 06:55:18 -0400
Subject: [PATCH 1/3] Documentation: Local dev request examples (#4189)
---
sample/configs/localdev-config.yaml | 26 ++++++++++++
sample/profiles/README.md | 2 +
sample/requests/localdev-test-request.http | 36 ++++++++++++++++
...localdev-test-stored-auction-response.http | 36 ++++++++++++++++
.../localdev-test-stored-bidder-response.http | 42 +++++++++++++++++++
.../sample-stored-auction-response.json | 14 +++++++
.../stored/sample-stored-bidder-response.json | 17 ++++++++
7 files changed, 173 insertions(+)
create mode 100644 sample/configs/localdev-config.yaml
create mode 100644 sample/profiles/README.md
create mode 100644 sample/requests/localdev-test-request.http
create mode 100644 sample/requests/localdev-test-stored-auction-response.http
create mode 100644 sample/requests/localdev-test-stored-bidder-response.http
create mode 100644 sample/stored/sample-stored-auction-response.json
create mode 100644 sample/stored/sample-stored-bidder-response.json
diff --git a/sample/configs/localdev-config.yaml b/sample/configs/localdev-config.yaml
new file mode 100644
index 00000000000..c2991cdf3b6
--- /dev/null
+++ b/sample/configs/localdev-config.yaml
@@ -0,0 +1,26 @@
+status-response: "ok"
+adapters:
+ generic:
+ enabled: true
+ endpoint: http://localhost
+cache:
+ scheme: http
+ host: localhost
+ path: /cache
+ query: uuid=
+settings:
+ enforce-valid-account: false
+ filesystem:
+ settings-filename: sample/configs/sample-app-settings.yaml
+ stored-requests-dir: sample/stored
+ stored-imps-dir: sample/stored
+ profiles-dir: sample/profiles
+ stored-responses-dir: sample/stored
+ categories-dir:
+gdpr:
+ default-value: 1
+ vendorlist:
+ v2:
+ cache-dir: /var/tmp/vendor2
+ v3:
+ cache-dir: /var/tmp/vendor3
diff --git a/sample/profiles/README.md b/sample/profiles/README.md
new file mode 100644
index 00000000000..ff7afdd219b
--- /dev/null
+++ b/sample/profiles/README.md
@@ -0,0 +1,2 @@
+### This is a directory to store profiles for file-based configuration.
+### Please put only valid OpenRTB JSON files here.
diff --git a/sample/requests/localdev-test-request.http b/sample/requests/localdev-test-request.http
new file mode 100644
index 00000000000..eb5da3d7b35
--- /dev/null
+++ b/sample/requests/localdev-test-request.http
@@ -0,0 +1,36 @@
+POST http://localhost:8080/openrtb2/auction
+Content-Type: application/json
+
+{
+ "id": "test-bid-request-id",
+ "site": {
+ "id": "test-site-id",
+ "name": "Test site",
+ "domain": "test.com",
+ "publisher": {
+ "id": "1001"
+ }
+ },
+ "regs": {
+ "gdpr": 0
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "prebid": {
+ "bidder": {
+ "generic": {}
+ }
+ }
+ }
+ }
+ ],
+ "test": 1
+}
+
+###
diff --git a/sample/requests/localdev-test-stored-auction-response.http b/sample/requests/localdev-test-stored-auction-response.http
new file mode 100644
index 00000000000..45509e61570
--- /dev/null
+++ b/sample/requests/localdev-test-stored-auction-response.http
@@ -0,0 +1,36 @@
+POST http://localhost:8080/openrtb2/auction
+Content-Type: application/json
+
+{
+ "id": "test-bid-request-id",
+ "site": {
+ "id": "test-site-id",
+ "name": "Test site",
+ "domain": "test.com",
+ "publisher": {
+ "id": "1001"
+ }
+ },
+ "regs": {
+ "gdpr": 0
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "w": 300,
+ "h": 250
+ }
+ }
+ ],
+ "ext": {
+ "prebid": {
+ "storedauctionresponse": {
+ "id": "sample-stored-auction-response"
+ }
+ }
+ },
+ "test": 1
+}
+
+###
diff --git a/sample/requests/localdev-test-stored-bidder-response.http b/sample/requests/localdev-test-stored-bidder-response.http
new file mode 100644
index 00000000000..eec448271e5
--- /dev/null
+++ b/sample/requests/localdev-test-stored-bidder-response.http
@@ -0,0 +1,42 @@
+POST http://localhost:8080/openrtb2/auction
+Content-Type: application/json
+
+{
+ "id": "test-bid-request-id",
+ "site": {
+ "id": "test-site-id",
+ "name": "Test site",
+ "domain": "test.com",
+ "publisher": {
+ "id": "1001"
+ }
+ },
+ "regs": {
+ "gdpr": 0
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "prebid": {
+ "bidder": {
+ "generic": {}
+ },
+ "storedbidresponse": [
+ {
+ "bidder": "generic",
+ "id": "sample-stored-bidder-response"
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "test": 1
+}
+
+###
diff --git a/sample/stored/sample-stored-auction-response.json b/sample/stored/sample-stored-auction-response.json
new file mode 100644
index 00000000000..34887d0fbb5
--- /dev/null
+++ b/sample/stored/sample-stored-auction-response.json
@@ -0,0 +1,14 @@
+{
+ "bid": [
+ {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adm": "
Ad Content
",
+ "crid": "creative123",
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "seat": "stored-auction-response-seat"
+}
diff --git a/sample/stored/sample-stored-bidder-response.json b/sample/stored/sample-stored-bidder-response.json
new file mode 100644
index 00000000000..e1dba2b29d1
--- /dev/null
+++ b/sample/stored/sample-stored-bidder-response.json
@@ -0,0 +1,17 @@
+{
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "1",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adm": "Ad Content
",
+ "crid": "creative123",
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ ]
+}
From 2383a149fc516b8674cafed9bc5134d14d2e5b86 Mon Sep 17 00:00:00 2001
From: Andrea Castello
Date: Fri, 26 Sep 2025 12:57:26 +0200
Subject: [PATCH 2/3] wurfl-devicedetection module enhancements (#4182)
---
.../devicedetection/v1/OrtbDeviceUpdater.java | 97 ++++++----
...LDeviceDetectionRawAuctionRequestHook.java | 19 ++
.../v1/OrtbDeviceUpdaterTest.java | 169 +++++++++---------
...iceDetectionRawAuctionRequestHookTest.java | 85 +++++++++
sample/configs/prebid-config-with-wurfl.yaml | 1 +
5 files changed, 249 insertions(+), 122 deletions(-)
diff --git a/extra/modules/wurfl-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdater.java b/extra/modules/wurfl-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdater.java
index b9b624b4f5b..ec79b79049c 100644
--- a/extra/modules/wurfl-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdater.java
+++ b/extra/modules/wurfl-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdater.java
@@ -55,6 +55,7 @@ public AuctionRequestPayload apply(AuctionRequestPayload auctionRequestPayload)
private Device update(Device ortbDevice) {
final String make = tryUpdateField(ortbDevice.getMake(), this::getWurflMake);
final String model = tryUpdateField(ortbDevice.getModel(), this::getWurflModel);
+ final String hwv = tryUpdateField(ortbDevice.getHwv(), this::getWurflModel);
final Integer deviceType = tryUpdateField(
Optional.ofNullable(ortbDevice.getDevicetype())
.filter(it -> it > 0)
@@ -72,6 +73,7 @@ private Device update(Device ortbDevice) {
.make(make)
.model(model)
.devicetype(deviceType)
+ .hwv(hwv)
.os(os)
.osv(osv)
.h(h)
@@ -103,49 +105,72 @@ private String getWurflModel() {
}
private Integer getWurflDeviceType() {
- try {
- if (wurflDevice.getVirtualCapabilityAsBool("is_mobile")) {
- // if at least one of these capabilities is not defined, the mobile device type is undefined
- final boolean isPhone = wurflDevice.getVirtualCapabilityAsBool("is_phone");
- final boolean isTablet = wurflDevice.getCapabilityAsBool("is_tablet");
- return isPhone || isTablet ? 1 : 6;
- }
- if (wurflDevice.getVirtualCapabilityAsBool("is_full_desktop")) {
- return 2;
- }
+ if (getWurflIsOtt()) {
+ return 7;
+ }
- if (wurflDevice.getCapabilityAsBool("is_connected_tv")) {
- return 3;
- }
+ if (getWurflIsConsole()) {
+ return 6;
+ }
- if (wurflDevice.getCapabilityAsBool("is_phone")) {
- return 4;
- }
+ if ("out_of_home_device".equals(getWurflPhysicalFormFactor())) {
+ return 8;
+ }
- if (wurflDevice.getCapabilityAsBool("is_tablet")) {
- return 5;
- }
+ final String formFactor = getWurflFormFactor();
+ return switch (formFactor) {
+ case "Desktop" -> 2;
+ case "Smartphone", "Feature Phone" -> 4;
+ case "Tablet" -> 5;
+ case "Smart-TV" -> 3;
+ case "Other Non-Mobile" -> 6;
+ case "Other Mobile" -> 1;
+ default -> null;
+ };
+ }
- if (wurflDevice.getCapabilityAsBool("is_ott")) {
- return 7;
- }
+ private Boolean getWurflIsOtt() {
+ try {
+ return wurflDevice.getCapabilityAsBool("is_ott");
+ } catch (CapabilityNotDefinedException e) {
+ logger.warn("Failed to get is_ott from WURFL device capabilities");
+ return Boolean.FALSE;
+ }
+ }
- final String physicalFormFactor = wurflDevice.getCapability("physical_form_factor");
- if (physicalFormFactor != null && physicalFormFactor.equals("out_of_home_device")) {
- return 8;
- }
- } catch (CapabilityNotDefinedException | VirtualCapabilityNotDefinedException | NumberFormatException e) {
- logger.warn("Failed to determine device type from WURFL device capabilities", e);
+ private String getWurflFormFactor() {
+ try {
+ return wurflDevice.getVirtualCapability("form_factor");
+ } catch (VirtualCapabilityNotDefinedException e) {
+ logger.warn("Failed to get form_factor from WURFL device capabilities");
+ return "";
+ }
+ }
+
+ private String getWurflPhysicalFormFactor() {
+ try {
+ return wurflDevice.getCapability("physical_form_factor");
+ } catch (CapabilityNotDefinedException e) {
+ logger.warn("Failed to get physical_form_factor from WURFL device capabilities");
+ return "";
+ }
+ }
+
+ private Boolean getWurflIsConsole() {
+ try {
+ return wurflDevice.getCapabilityAsBool("is_console");
+ } catch (CapabilityNotDefinedException e) {
+ logger.warn("Failed to get is_console from WURFL device capabilities");
+ return Boolean.FALSE;
}
- return null;
}
private String getWurflOs() {
try {
return wurflDevice.getVirtualCapability("advertised_device_os");
} catch (VirtualCapabilityNotDefinedException e) {
- logger.warn("Failed to evaluate advertised device OS", e);
+ logger.warn("Failed to evaluate advertised device OS");
return null;
}
}
@@ -154,7 +179,7 @@ private String getWurflOsv() {
try {
return wurflDevice.getVirtualCapability("advertised_device_os_version");
} catch (VirtualCapabilityNotDefinedException e) {
- logger.warn("Failed to evaluate advertised device OS version", e);
+ logger.warn("Failed to evaluate advertised device OS version");
}
return null;
}
@@ -163,7 +188,7 @@ private Integer getWurflH() {
try {
return wurflDevice.getCapabilityAsInt("resolution_height");
} catch (NumberFormatException e) {
- logger.warn("Failed to get resolution height from WURFL device capabilities", e);
+ logger.warn("Failed to get resolution height from WURFL device capabilities");
return null;
}
}
@@ -172,7 +197,7 @@ private Integer getWurflW() {
try {
return wurflDevice.getCapabilityAsInt("resolution_width");
} catch (NumberFormatException e) {
- logger.warn("Failed to get resolution width from WURFL device capabilities", e);
+ logger.warn("Failed to get resolution width from WURFL device capabilities");
return null;
}
}
@@ -181,7 +206,7 @@ private Integer getWurflPpi() {
try {
return wurflDevice.getVirtualCapabilityAsInt("pixel_density");
} catch (VirtualCapabilityNotDefinedException e) {
- logger.warn("Failed to get pixel density from WURFL device capabilities", e);
+ logger.warn("Failed to get pixel density from WURFL device capabilities");
return null;
}
}
@@ -193,7 +218,7 @@ private BigDecimal getWurflPxRatio() {
? new BigDecimal(densityAsString)
: null;
} catch (CapabilityNotDefinedException | NumberFormatException e) {
- logger.warn("Failed to get pixel ratio from WURFL device capabilities", e);
+ logger.warn("Failed to get pixel ratio from WURFL device capabilities");
return null;
}
}
@@ -202,7 +227,7 @@ private Integer getWurflJs() {
try {
return wurflDevice.getCapabilityAsBool("ajax_support_javascript") ? 1 : 0;
} catch (CapabilityNotDefinedException | NumberFormatException e) {
- logger.warn("Failed to get JS support from WURFL device capabilities", e);
+ logger.warn("Failed to get JS support from WURFL device capabilities");
return null;
}
}
diff --git a/extra/modules/wurfl-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHook.java b/extra/modules/wurfl-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHook.java
index 337343f15d4..5a92e051f0c 100644
--- a/extra/modules/wurfl-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHook.java
+++ b/extra/modules/wurfl-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHook.java
@@ -1,6 +1,7 @@
package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1;
import com.iab.openrtb.request.Device;
+import org.prebid.server.proto.openrtb.ext.request.ExtDevice;
import com.iab.openrtb.request.BidRequest;
import org.prebid.server.log.Logger;
import org.prebid.server.log.LoggerFactory;
@@ -31,6 +32,7 @@ public class WURFLDeviceDetectionRawAuctionRequestHook implements RawAuctionRequ
private static final Logger logger = LoggerFactory.getLogger(WURFLDeviceDetectionRawAuctionRequestHook.class);
public static final String CODE = "wurfl-devicedetection-raw-auction-request";
+ private static final String WURFL_PROPERTY = "wurfl";
private final WURFLService wurflService;
private final Set allowedPublisherIDs;
@@ -62,6 +64,11 @@ public Future> call(AuctionRequestPayloa
return noActionResult();
}
+ if (isDeviceAlreadyEnriched(device)) {
+ logger.info("Device is already enriched, returning original bid request");
+ return noActionResult();
+ }
+
final Map requestHeaders =
invocationContext.moduleContext() instanceof AuctionRequestHeadersContext moduleContext
? moduleContext.getHeaders()
@@ -87,6 +94,18 @@ public Future> call(AuctionRequestPayloa
.build());
}
+ private boolean isDeviceAlreadyEnriched(Device device) {
+ final ExtDevice extDevice = device.getExt();
+ if (extDevice != null && extDevice.containsProperty(WURFL_PROPERTY)) {
+ return true;
+ }
+
+ // Check if other some of the other Device data are already set
+ final Integer deviceType = device.getDevicetype();
+ final String hwv = device.getHwv();
+ return deviceType != null && deviceType > 0 && StringUtils.isNotEmpty(hwv);
+ }
+
private boolean shouldEnrichDevice(AuctionInvocationContext invocationContext) {
return CollectionUtils.isEmpty(allowedPublisherIDs) || isAccountValid(invocationContext.auctionContext());
}
diff --git a/extra/modules/wurfl-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdaterTest.java b/extra/modules/wurfl-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdaterTest.java
index 54e7464619d..43300b0ff92 100644
--- a/extra/modules/wurfl-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdaterTest.java
+++ b/extra/modules/wurfl-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdaterTest.java
@@ -2,6 +2,7 @@
import com.fasterxml.jackson.databind.node.TextNode;
import com.iab.openrtb.request.Device;
+import com.scientiamobile.wurfl.core.exc.VirtualCapabilityNotDefinedException;
import org.mockito.Mock;
import org.prebid.server.proto.openrtb.ext.request.ExtDevice;
import org.junit.jupiter.api.BeforeEach;
@@ -19,6 +20,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mock.Strictness.LENIENT;
+import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class OrtbDeviceUpdaterTest {
@@ -46,13 +48,10 @@ void setUp() {
@Test
public void updateShouldUpdateDeviceMakeWhenOriginalIsEmpty() {
// given
- given(wurflDevice.getCapability("brand_name")).willReturn("Apple");
- given(wurflDevice.getCapability("model_name")).willReturn("iPhone");
- given(wurflDevice.getCapabilityAsBool("ajax_support_javascript")).willReturn(true);
- given(wurflDevice.getVirtualCapabilityAsBool("is_mobile")).willReturn(true);
- given(wurflDevice.getVirtualCapabilityAsBool("is_phone")).willReturn(true);
+ given(wurflDevice.getCapability("brand_name")).willReturn("TestPhone");
given(wurflDevice.getCapabilityAsBool("is_tablet")).willReturn(false);
given(wurflDevice.getVirtualCapabilityAsBool("is_full_desktop")).willReturn(false);
+ given(wurflDevice.getVirtualCapability("form_factor")).willReturn("Smartphone");
final OrtbDeviceUpdater target = new OrtbDeviceUpdater(wurflDevice, staticCaps, virtualCaps, true, mapper);
final Device device = Device.builder().build();
final BidRequest bidRequest = BidRequest.builder().device(device).build();
@@ -62,22 +61,18 @@ public void updateShouldUpdateDeviceMakeWhenOriginalIsEmpty() {
// then
final Device resultDevice = result.bidRequest().getDevice();
- assertThat(resultDevice.getMake()).isEqualTo("Apple");
- assertThat(resultDevice.getDevicetype()).isEqualTo(1);
+ assertThat(resultDevice.getMake()).isEqualTo("TestPhone");
+ assertThat(resultDevice.getDevicetype()).isEqualTo(4);
}
@Test
public void updateShouldNotUpdateDeviceMakeWhenOriginalExists() {
// given
- final Device device = Device.builder().make("Samsung").build();
+ final Device device = Device.builder().make("Samphone").model("youPhone").build();
final BidRequest bidRequest = BidRequest.builder().device(device).build();
- given(wurflDevice.getCapability("brand_name")).willReturn("Apple");
- given(wurflDevice.getCapability("model_name")).willReturn("iPhone");
- given(wurflDevice.getCapability("ajax_support_javascript")).willReturn("true");
- given(wurflDevice.getVirtualCapability("is_mobile")).willReturn("true");
- given(wurflDevice.getVirtualCapability("is_phone")).willReturn("true");
- given(wurflDevice.getCapability("is_tablet")).willReturn("false");
- given(wurflDevice.getVirtualCapability("is_full_desktop")).willReturn("false");
+ given(wurflDevice.getCapability("brand_name")).willReturn("TestPhone");
+ given(wurflDevice.getCapability("model_name")).willReturn("youPhone");
+ given(wurflDevice.getVirtualCapability("form_factor")).willReturn("Smartphone");
final OrtbDeviceUpdater target = new OrtbDeviceUpdater(wurflDevice, staticCaps, virtualCaps, true, mapper);
given(payload.bidRequest()).willReturn(bidRequest);
@@ -86,21 +81,18 @@ public void updateShouldNotUpdateDeviceMakeWhenOriginalExists() {
// then
final Device resultDevice = result.bidRequest().getDevice();
- assertThat(resultDevice.getMake()).isEqualTo("Samsung");
+ assertThat(resultDevice.getMake()).isEqualTo("Samphone");
+ assertThat(resultDevice.getModel()).isEqualTo("youPhone");
}
@Test
public void updateShouldNotUpdateDeviceMakeWhenOriginalBigIntegerExists() {
// given
- final Device device = Device.builder().make("Apple").pxratio(new BigDecimal("1.0")).build();
+ final Device device = Device.builder().make("TestPhone").pxratio(new BigDecimal("1.0")).build();
final BidRequest bidRequest = BidRequest.builder().device(device).build();
- given(wurflDevice.getCapability("brand_name")).willReturn("Apple");
- given(wurflDevice.getCapability("model_name")).willReturn("iPhone");
- given(wurflDevice.getCapability("ajax_support_javascript")).willReturn("true");
- given(wurflDevice.getVirtualCapability("is_mobile")).willReturn("true");
- given(wurflDevice.getVirtualCapability("is_phone")).willReturn("true");
- given(wurflDevice.getCapability("is_tablet")).willReturn("false");
- given(wurflDevice.getVirtualCapability("is_full_desktop")).willReturn("false");
+ given(wurflDevice.getCapability("brand_name")).willReturn("TestPhone");
+ given(wurflDevice.getCapability("density_class")).willReturn("2.2");
+ given(wurflDevice.getVirtualCapability("form_factor")).willReturn("Smartphone");
final OrtbDeviceUpdater target = new OrtbDeviceUpdater(wurflDevice, staticCaps, virtualCaps, true, mapper);
given(payload.bidRequest()).willReturn(bidRequest);
@@ -109,7 +101,7 @@ public void updateShouldNotUpdateDeviceMakeWhenOriginalBigIntegerExists() {
// then
final Device resultDevice = result.bidRequest().getDevice();
- assertThat(resultDevice.getMake()).isEqualTo("Apple");
+ assertThat(resultDevice.getMake()).isEqualTo("TestPhone");
assertThat(resultDevice.getPxratio()).isEqualTo("1.0");
}
@@ -118,13 +110,8 @@ public void updateShouldUpdateDeviceModelWhenOriginalIsEmpty() {
// given
final Device device = Device.builder().build();
final BidRequest bidRequest = BidRequest.builder().device(device).build();
- given(wurflDevice.getCapability("brand_name")).willReturn("Apple");
- given(wurflDevice.getCapability("model_name")).willReturn("iPhone");
- given(wurflDevice.getCapability("ajax_support_javascript")).willReturn("true");
- given(wurflDevice.getVirtualCapability("is_mobile")).willReturn("true");
- given(wurflDevice.getVirtualCapability("is_phone")).willReturn("true");
- given(wurflDevice.getCapability("is_tablet")).willReturn("false");
- given(wurflDevice.getVirtualCapability("is_full_desktop")).willReturn("false");
+ given(wurflDevice.getCapability("model_name")).willReturn("youPhone");
+ given(wurflDevice.getVirtualCapability("form_factor")).willReturn("Smartphone");
final OrtbDeviceUpdater target = new OrtbDeviceUpdater(wurflDevice, staticCaps, virtualCaps, true, mapper);
given(payload.bidRequest()).willReturn(bidRequest);
@@ -133,7 +120,7 @@ public void updateShouldUpdateDeviceModelWhenOriginalIsEmpty() {
// then
final Device resultDevice = result.bidRequest().getDevice();
- assertThat(resultDevice.getModel()).isEqualTo("iPhone");
+ assertThat(resultDevice.getModel()).isEqualTo("youPhone");
}
@Test
@@ -141,10 +128,8 @@ public void updateShouldUpdateDeviceOsWhenOriginalIsEmpty() {
// given
final Device device = Device.builder().build();
final BidRequest bidRequest = BidRequest.builder().device(device).build();
- given(wurflDevice.getCapability("brand_name")).willReturn("Apple");
- given(wurflDevice.getCapability("model_name")).willReturn("iPhone");
- given(wurflDevice.getVirtualCapability("advertised_device_os")).willReturn("iOS");
- given(wurflDevice.getVirtualCapabilityAsBool("is_full_desktop")).willReturn(false);
+ given(wurflDevice.getVirtualCapability("advertised_device_os")).willReturn("testOS");
+ given(wurflDevice.getVirtualCapability("form_factor")).willReturn("Smartphone");
final OrtbDeviceUpdater target = new OrtbDeviceUpdater(wurflDevice, staticCaps, virtualCaps, true, mapper);
given(payload.bidRequest()).willReturn(bidRequest);
@@ -153,7 +138,7 @@ public void updateShouldUpdateDeviceOsWhenOriginalIsEmpty() {
// then
final Device resultDevice = result.bidRequest().getDevice();
- assertThat(resultDevice.getOs()).isEqualTo("iOS");
+ assertThat(resultDevice.getOs()).isEqualTo("testOS");
}
@Test
@@ -161,13 +146,7 @@ public void updateShouldUpdateResolutionWhenOriginalIsEmpty() {
// given
final Device device = Device.builder().build();
final BidRequest bidRequest = BidRequest.builder().device(device).build();
- given(wurflDevice.getCapability("brand_name")).willReturn("Apple");
- given(wurflDevice.getCapability("model_name")).willReturn("iPhone");
- given(wurflDevice.getCapabilityAsBool("ajax_support_javascript")).willReturn(true);
- given(wurflDevice.getVirtualCapabilityAsBool("is_mobile")).willReturn(true);
- given(wurflDevice.getVirtualCapabilityAsBool("is_phone")).willReturn(true);
- given(wurflDevice.getVirtualCapabilityAsBool("is_tablet")).willReturn(false);
- given(wurflDevice.getVirtualCapabilityAsBool("is_full_desktop")).willReturn(false);
+ given(wurflDevice.getVirtualCapability("form_factor")).willReturn("Smartphone");
given(wurflDevice.getCapabilityAsInt("resolution_width")).willReturn(3200);
given(wurflDevice.getCapabilityAsInt("resolution_height")).willReturn(1440);
final OrtbDeviceUpdater target = new OrtbDeviceUpdater(wurflDevice, staticCaps, virtualCaps, true, mapper);
@@ -188,6 +167,7 @@ public void updateShouldHandleJavascriptSupport() {
final BidRequest bidRequest = BidRequest.builder().device(device).build();
given(wurflDevice.getCapabilityAsBool("ajax_support_javascript")).willReturn(true);
given(wurflDevice.getVirtualCapabilityAsBool("is_mobile")).willReturn(true);
+ given(wurflDevice.getVirtualCapability("form_factor")).willReturn("Smartphone");
final OrtbDeviceUpdater target = new OrtbDeviceUpdater(wurflDevice, staticCaps, virtualCaps, true, mapper);
given(payload.bidRequest()).willReturn(bidRequest);
@@ -205,10 +185,6 @@ public void updateShouldHandleOttDeviceType() {
final Device device = Device.builder().build();
final BidRequest bidRequest = BidRequest.builder().device(device).build();
given(wurflDevice.getCapabilityAsBool("is_ott")).willReturn(true);
- given(wurflDevice.getVirtualCapabilityAsBool("is_full_desktop")).willReturn(false);
- given(wurflDevice.getVirtualCapabilityAsBool("is_mobile")).willReturn(false);
- given(wurflDevice.getVirtualCapabilityAsBool("is_phone")).willReturn(false);
- given(wurflDevice.getCapabilityAsBool("is_tablet")).willReturn(false);
final OrtbDeviceUpdater target = new OrtbDeviceUpdater(wurflDevice, staticCaps, virtualCaps, true, mapper);
given(payload.bidRequest()).willReturn(bidRequest);
// when
@@ -225,8 +201,6 @@ public void updateShouldHandleOutOfHomeDeviceType() {
final Device device = Device.builder().build();
final BidRequest bidRequest = BidRequest.builder().device(device).build();
given(wurflDevice.getCapability("physical_form_factor")).willReturn("out_of_home_device");
- given(wurflDevice.getCapabilityAsBool("is_tablet")).willReturn(false);
- given(wurflDevice.getCapabilityAsBool("is_wireless_device")).willReturn(false);
final OrtbDeviceUpdater target = new OrtbDeviceUpdater(wurflDevice, staticCaps, virtualCaps, true, mapper);
given(payload.bidRequest()).willReturn(bidRequest);
// when
@@ -242,12 +216,7 @@ public void updateShouldReturnDeviceOtherMobileWhenMobileIsNotPhoneOrTablet() {
// given
final Device device = Device.builder().build();
final BidRequest bidRequest = BidRequest.builder().device(device).build();
- given(wurflDevice.getVirtualCapabilityAsBool("is_mobile")).willReturn(true);
- given(wurflDevice.getVirtualCapabilityAsBool("is_phone")).willReturn(false);
- given(wurflDevice.getCapabilityAsBool("is_tablet")).willReturn(false);
- given(wurflDevice.getVirtualCapabilityAsBool("is_full_desktop")).willReturn(false);
- given(wurflDevice.getVirtualCapability("advertised_device_os")).willReturn("TestOs");
- given(wurflDevice.getVirtualCapability("advertised_device_os_version")).willReturn("1.0");
+ given(wurflDevice.getVirtualCapability("form_factor")).willReturn("Other Non-Mobile");
final OrtbDeviceUpdater target = new OrtbDeviceUpdater(wurflDevice, staticCaps, virtualCaps, true, mapper);
given(payload.bidRequest()).willReturn(bidRequest);
@@ -259,12 +228,28 @@ public void updateShouldReturnDeviceOtherMobileWhenMobileIsNotPhoneOrTablet() {
}
@Test
- public void updateShouldReturnNullWhenMobileTypeIsUnknown() {
+ public void updateShouldReturnNullDeviceTypeWhenMobileTypeIsUnknown() {
+ // given
+ given(wurflDevice.getVirtualCapability("form_factor")).willReturn("Other non-Mobile");
+ final Device device = Device.builder().build();
+ final BidRequest bidRequest = BidRequest.builder().device(device).build();
+ final OrtbDeviceUpdater target = new OrtbDeviceUpdater(wurflDevice, staticCaps, virtualCaps, true, mapper);
+ // when
+ given(payload.bidRequest()).willReturn(bidRequest);
+ final AuctionRequestPayload result = target.apply(payload);
+ // then
+ final Device resultDevice = result.bidRequest().getDevice();
+ assertThat(resultDevice.getDevicetype()).isNull();
+ }
+
+ @Test
+ public void updateShouldReturnNullDeviceTypeWhenFormFactorIsMissing() {
// given
- given(wurflDevice.getVirtualCapability("is_mobile")).willReturn("false");
+ given(wurflDevice.getVirtualCapability("is_mobile")).willReturn("true");
given(wurflDevice.getVirtualCapability("is_phone")).willReturn("false");
given(wurflDevice.getCapability("is_tablet")).willReturn("false");
- given(wurflDevice.getVirtualCapability("is_full_desktop")).willReturn("false");
+ when(wurflDevice.getVirtualCapability("form_factor"))
+ .thenThrow(new VirtualCapabilityNotDefinedException("form_factor"));
final Device device = Device.builder().build();
final BidRequest bidRequest = BidRequest.builder().device(device).build();
final OrtbDeviceUpdater target = new OrtbDeviceUpdater(wurflDevice, staticCaps, virtualCaps, true, mapper);
@@ -276,17 +261,27 @@ public void updateShouldReturnNullWhenMobileTypeIsUnknown() {
assertThat(resultDevice.getDevicetype()).isNull();
}
+ @Test
+ public void updateShouldReturnGenericMobileTabletDeviceTypeWhenFormFactorIsOtherMobile() {
+ // given
+ given(wurflDevice.getCapability("is_tablet")).willReturn("false");
+ given(wurflDevice.getVirtualCapability("form_factor")).willReturn("Other Mobile");
+ final Device device = Device.builder().build();
+ final BidRequest bidRequest = BidRequest.builder().device(device).build();
+ final OrtbDeviceUpdater target = new OrtbDeviceUpdater(wurflDevice, staticCaps, virtualCaps, true, mapper);
+ // when
+ given(payload.bidRequest()).willReturn(bidRequest);
+ final AuctionRequestPayload result = target.apply(payload);
+ // then
+ final Device resultDevice = result.bidRequest().getDevice();
+ assertThat(resultDevice.getDevicetype()).isEqualTo(1);
+ }
+
@Test
public void updateShouldHandlePersonalComputerDeviceType() {
// given
final Device device = Device.builder().build();
final BidRequest bidRequest = BidRequest.builder().device(device).build();
- given(wurflDevice.getVirtualCapabilityAsBool("is_mobile")).willReturn(false);
- given(wurflDevice.getVirtualCapabilityAsBool("is_phone")).willReturn(false);
- given(wurflDevice.getCapabilityAsBool("is_tablet")).willReturn(false);
- given(wurflDevice.getVirtualCapabilityAsBool("is_full_desktop")).willReturn(true);
- given(wurflDevice.getVirtualCapability("advertised_device_os")).willReturn("Windows");
- given(wurflDevice.getVirtualCapability("advertised_device_os_version")).willReturn("10");
given(wurflDevice.getVirtualCapability("form_factor")).willReturn("Desktop");
final OrtbDeviceUpdater target = new OrtbDeviceUpdater(wurflDevice, staticCaps, virtualCaps, true, mapper);
// when
@@ -303,14 +298,7 @@ public void updateShouldHandleConnectedTvDeviceType() {
// given
final Device device = Device.builder().build();
final BidRequest bidRequest = BidRequest.builder().device(device).build();
- given(wurflDevice.getCapabilityAsBool("is_connected_tv")).willReturn(true);
- given(wurflDevice.getVirtualCapabilityAsBool("is_full_desktop")).willReturn(false);
- given(wurflDevice.getVirtualCapabilityAsBool("is_mobile")).willReturn(false);
- given(wurflDevice.getVirtualCapabilityAsBool("is_phone")).willReturn(false);
- given(wurflDevice.getVirtualCapabilityAsBool("is_tablet")).willReturn(false);
- given(wurflDevice.getVirtualCapability("advertised_device_os")).willReturn("WebOS");
- given(wurflDevice.getVirtualCapability("advertised_device_os_version")).willReturn("4");
- given(wurflDevice.getCapabilityAsBool("is_connected_tv")).willReturn(true);
+ given(wurflDevice.getVirtualCapability("form_factor")).willReturn("Smart-TV");
final OrtbDeviceUpdater target = new OrtbDeviceUpdater(wurflDevice, staticCaps, virtualCaps, true, mapper);
// when
given(payload.bidRequest()).willReturn(bidRequest);
@@ -326,10 +314,6 @@ public void updateShouldNotUpdateDeviceTypeWhenSet() {
final Device device = Device.builder()
.devicetype(3)
.build();
- given(wurflDevice.getVirtualCapabilityAsBool("is_full_desktop")).willReturn(true);
- given(wurflDevice.getVirtualCapabilityAsBool("is_mobile")).willReturn(false);
- given(wurflDevice.getVirtualCapabilityAsBool("is_phone")).willReturn(false);
- given(wurflDevice.getVirtualCapabilityAsBool("is_tablet")).willReturn(false); // device type 2
final BidRequest bidRequest = BidRequest.builder().device(device).build();
final OrtbDeviceUpdater target = new OrtbDeviceUpdater(wurflDevice, staticCaps, virtualCaps, true, mapper);
// when
@@ -343,12 +327,7 @@ public void updateShouldNotUpdateDeviceTypeWhenSet() {
@Test
public void updateShouldHandleTabletDeviceType() {
// given
- given(wurflDevice.getCapabilityAsBool("is_tablet")).willReturn(true);
- given(wurflDevice.getVirtualCapabilityAsBool("is_full_desktop")).willReturn(false);
- given(wurflDevice.getVirtualCapabilityAsBool("is_mobile")).willReturn(false);
- given(wurflDevice.getVirtualCapabilityAsBool("is_phone")).willReturn(false);
- given(wurflDevice.getCapability("brand_name")).willReturn("Samsung");
- given(wurflDevice.getCapability("model_name")).willReturn("Galaxy Tab S9+");
+ given(wurflDevice.getVirtualCapability("form_factor")).willReturn("Tablet");
final Device device = Device.builder().build();
final BidRequest bidRequest = BidRequest.builder().device(device).build();
@@ -362,6 +341,23 @@ public void updateShouldHandleTabletDeviceType() {
assertThat(resultDevice.getDevicetype()).isEqualTo(5);
}
+ @Test
+ public void updateShouldHandlePhoneDeviceType() {
+ // given
+ given(wurflDevice.getVirtualCapability("form_factor")).willReturn("Smartphone");
+
+ final Device device = Device.builder().build();
+ final BidRequest bidRequest = BidRequest.builder().device(device).build();
+ final OrtbDeviceUpdater target = new OrtbDeviceUpdater(wurflDevice, staticCaps, virtualCaps, true, mapper);
+ given(payload.bidRequest()).willReturn(bidRequest);
+ // when
+ final AuctionRequestPayload result = target.apply(payload);
+
+ // then
+ final Device resultDevice = result.bidRequest().getDevice();
+ assertThat(resultDevice.getDevicetype()).isEqualTo(4);
+ }
+
@Test
public void updateShouldAddWurflPropertyToExtIfMissingAndPreserveExistingProperties() {
// given
@@ -371,13 +367,14 @@ public void updateShouldAddWurflPropertyToExtIfMissingAndPreserveExistingPropert
.ext(existingExt)
.build();
final BidRequest bidRequest = BidRequest.builder().device(device).build();
- given(wurflDevice.getCapability("brand_name")).willReturn("Apple");
- given(wurflDevice.getCapability("model_name")).willReturn("iPhone");
+ given(wurflDevice.getCapability("brand_name")).willReturn("TestPhone");
+ given(wurflDevice.getCapability("model_name")).willReturn("youPhone");
given(wurflDevice.getCapability("ajax_support_javascript")).willReturn("true");
given(wurflDevice.getVirtualCapability("is_mobile")).willReturn("true");
given(wurflDevice.getVirtualCapability("is_phone")).willReturn("true");
given(wurflDevice.getCapability("is_tablet")).willReturn("false");
given(wurflDevice.getVirtualCapability("is_full_desktop")).willReturn("false");
+ given(wurflDevice.getVirtualCapability("form_factor")).willReturn("Smartphone");
final Set staticCaps = Set.of("brand_name");
final Set virtualCaps = Set.of("advertised_device_os");
final OrtbDeviceUpdater target = new OrtbDeviceUpdater(wurflDevice, staticCaps, virtualCaps, true, mapper);
diff --git a/extra/modules/wurfl-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHookTest.java b/extra/modules/wurfl-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHookTest.java
index 8d82ebb0209..f981f43ccaa 100644
--- a/extra/modules/wurfl-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHookTest.java
+++ b/extra/modules/wurfl-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHookTest.java
@@ -8,6 +8,9 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import org.prebid.server.proto.openrtb.ext.request.ExtDevicePrebid;
+import org.prebid.server.proto.openrtb.ext.request.ExtDevice;
+import org.prebid.server.proto.openrtb.ext.request.ExtDeviceInt;
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.settings.model.Account;
import org.prebid.server.json.JacksonMapper;
@@ -20,6 +23,7 @@
import org.prebid.server.hooks.v1.auction.AuctionInvocationContext;
import org.prebid.server.hooks.v1.auction.AuctionRequestPayload;
import org.prebid.server.model.CaseInsensitiveMultiMap;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.Collections;
import java.util.Map;
@@ -87,6 +91,87 @@ public void callShouldReturnNoActionWhenDeviceIsNull() {
assertThat(result.action()).isEqualTo(InvocationAction.no_action);
}
+ @Test
+ public void callShouldReturnNoActionWhenDeviceHasWurflProperty() {
+ // given
+ final String ua = "Mozilla/5.0 (testPhone; CPU testPhone OS 1_0_2) Version/17.4.1 Mobile/12E GrandTest/604.1";
+ final ExtDevicePrebid extDevicePrebid = ExtDevicePrebid.of(ExtDeviceInt.of(80, 80));
+ final ExtDevice extDevice = ExtDevice.of(0, extDevicePrebid);
+ final ObjectNode wurfl = mapper.mapper().createObjectNode();
+ wurfl.put("wurfl_id", "test_phone_ver1");
+ extDevice.addProperty("wurfl", wurfl);
+ final Device device = Device.builder()
+ .ua(ua)
+ .ext(extDevice)
+ .build();
+ final BidRequest bidRequest = BidRequest.builder().device(device).build();
+ given(payload.bidRequest()).willReturn(bidRequest);
+ given(configProperties.getAllowedPublisherIds()).willReturn(Collections.emptySet());
+ final WURFLService wurflService = new WURFLService(wurflEngine, configProperties);
+ target = new WURFLDeviceDetectionRawAuctionRequestHook(wurflService, configProperties, mapper);
+
+ // when
+ final InvocationResult result = target.call(payload, context).result();
+
+ // then
+ assertThat(result.status()).isEqualTo(InvocationStatus.success);
+ assertThat(result.action()).isEqualTo(InvocationAction.no_action);
+ }
+
+ @Test
+ public void callShouldReturnNoActionWhenDeviceHasDeviceTypeAndHwv() {
+ // given
+ final String ua = "Mozilla/5.0 (testPhone; CPU testPhone OS 1_0_2) Version/17.4.1 Mobile/12E GrandTest/604.1";
+ final Device device = Device.builder()
+ .ua(ua)
+ .hwv("test_phone_ver1")
+ .devicetype(1)
+ .build();
+ final BidRequest bidRequest = BidRequest.builder().device(device).build();
+ given(payload.bidRequest()).willReturn(bidRequest);
+ given(configProperties.getAllowedPublisherIds()).willReturn(Collections.emptySet());
+ final WURFLService wurflService = new WURFLService(wurflEngine, configProperties);
+ given(wurflDevice.getId()).willReturn("test_phone_ver1");
+ target = new WURFLDeviceDetectionRawAuctionRequestHook(wurflService, configProperties, mapper);
+
+ // when
+ final InvocationResult result = target.call(payload, context).result();
+
+ // then
+ assertThat(result.status()).isEqualTo(InvocationStatus.success);
+ assertThat(result.action()).isEqualTo(InvocationAction.no_action);
+ }
+
+ @Test
+ public void callShouldReturnActionUpdateWhenDeviceHasJustDeviceType() {
+ // given
+ final String ua = "Mozilla/5.0 (testPhone; CPU testPhone OS 1_0_2) Version/17.4.1 Mobile/12E GrandTest/604.1";
+ given(configProperties.getAllowedPublisherIds()).willReturn(Collections.emptySet());
+ final WURFLService wurflService = new WURFLService(wurflEngine, configProperties);
+ target = new WURFLDeviceDetectionRawAuctionRequestHook(wurflService, configProperties, mapper);
+ final Device device = Device.builder()
+ .ua(ua)
+ .devicetype(1)
+ .build();
+ final BidRequest bidRequest = BidRequest.builder().device(device).build();
+ given(payload.bidRequest()).willReturn(bidRequest);
+ final CaseInsensitiveMultiMap headers = CaseInsensitiveMultiMap.builder()
+ .add("User-Agent", ua)
+ .build();
+ final AuctionRequestHeadersContext headersContext = AuctionRequestHeadersContext.from(headers);
+
+ given(context.moduleContext()).willReturn(headersContext);
+ given(wurflEngine.getDeviceForRequest(any(Map.class))).willReturn(wurflDevice);
+ given(wurflDevice.getId()).willReturn("test_phone_ver1");
+
+ // when
+ final InvocationResult result = target.call(payload, context).result();
+
+ // then
+ assertThat(result.status()).isEqualTo(InvocationStatus.success);
+ assertThat(result.action()).isEqualTo(InvocationAction.update);
+ }
+
@Test
public void callShouldUpdateDeviceWhenWurflDeviceIsDetected() throws Exception {
// given
diff --git a/sample/configs/prebid-config-with-wurfl.yaml b/sample/configs/prebid-config-with-wurfl.yaml
index 5dff90201ee..0ce8abebe17 100644
--- a/sample/configs/prebid-config-with-wurfl.yaml
+++ b/sample/configs/prebid-config-with-wurfl.yaml
@@ -23,6 +23,7 @@ settings:
filesystem:
settings-filename: sample/configs/sample-app-settings.yaml
stored-requests-dir: sample
+ profiles-dir: sample
stored-imps-dir: sample
stored-responses-dir: sample
categories-dir:
From 19e3285db0e3ccec315c1c6cee6ded9f23f8560f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gabriela=20Mi=C4=99dlar?=
<155444733+gmiedlar-ox@users.noreply.github.com>
Date: Fri, 26 Sep 2025 12:57:45 +0200
Subject: [PATCH 3/3] OpenX Adapter: Set buyer exts fields in
`bid.ext.prebid.meta` (#4171)
---
.../server/bidder/openx/OpenxBidder.java | 55 ++++++-
.../bidder/openx/proto/OpenxBidExt.java | 15 ++
.../server/bidder/openx/OpenxBidderTest.java | 150 ++++++++++++++++++
3 files changed, 216 insertions(+), 4 deletions(-)
create mode 100644 src/main/java/org/prebid/server/bidder/openx/proto/OpenxBidExt.java
diff --git a/src/main/java/org/prebid/server/bidder/openx/OpenxBidder.java b/src/main/java/org/prebid/server/bidder/openx/OpenxBidder.java
index 09412abe65f..00a8c8dc3fb 100644
--- a/src/main/java/org/prebid/server/bidder/openx/OpenxBidder.java
+++ b/src/main/java/org/prebid/server/bidder/openx/OpenxBidder.java
@@ -17,6 +17,7 @@
import org.prebid.server.bidder.model.HttpRequest;
import org.prebid.server.bidder.model.Result;
import org.prebid.server.bidder.openx.model.OpenxImpType;
+import org.prebid.server.bidder.openx.proto.OpenxBidExt;
import org.prebid.server.bidder.openx.proto.OpenxBidResponse;
import org.prebid.server.bidder.openx.proto.OpenxBidResponseExt;
import org.prebid.server.bidder.openx.proto.OpenxRequestExt;
@@ -29,6 +30,8 @@
import org.prebid.server.proto.openrtb.ext.request.ExtRequest;
import org.prebid.server.proto.openrtb.ext.request.openx.ExtImpOpenx;
import org.prebid.server.proto.openrtb.ext.response.BidType;
+import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid;
+import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidMeta;
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidVideo;
import org.prebid.server.proto.openrtb.ext.response.ExtIgi;
import org.prebid.server.proto.openrtb.ext.response.ExtIgiIgs;
@@ -270,13 +273,13 @@ private ObjectNode makeImpExt(ObjectNode impExt, boolean addCustomParams) {
return openxImpExt;
}
- private static List extractBids(BidRequest bidRequest, OpenxBidResponse bidResponse) {
+ private List extractBids(BidRequest bidRequest, OpenxBidResponse bidResponse) {
return bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())
? Collections.emptyList()
: bidsFromResponse(bidRequest, bidResponse);
}
- private static List bidsFromResponse(BidRequest bidRequest, OpenxBidResponse bidResponse) {
+ private List bidsFromResponse(BidRequest bidRequest, OpenxBidResponse bidResponse) {
final Map impIdToBidType = impIdToBidType(bidRequest);
final String bidCurrency = StringUtils.isNotBlank(bidResponse.getCur())
@@ -292,11 +295,11 @@ private static List bidsFromResponse(BidRequest bidRequest, OpenxBidR
.toList();
}
- private static BidderBid toBidderBid(Bid bid, Map impIdToBidType, String bidCurrency) {
+ private BidderBid toBidderBid(Bid bid, Map impIdToBidType, String bidCurrency) {
final BidType bidType = getBidType(bid, impIdToBidType);
final ExtBidPrebidVideo videoInfo = bidType == BidType.video ? getVideoInfo(bid) : null;
return BidderBid.builder()
- .bid(bid)
+ .bid(bid.toBuilder().ext(getBidExt(bid)).build())
.type(bidType)
.bidCurrency(bidCurrency)
.videoInfo(videoInfo)
@@ -334,4 +337,48 @@ private static List extractIgi(OpenxBidResponse bidResponse) {
return igs.isEmpty() ? null : Collections.singletonList(ExtIgi.builder().igs(igs).build());
}
+
+ private ObjectNode getBidExt(Bid bid) {
+ final ObjectNode ext = bid.getExt();
+ if (ext == null) {
+ return null;
+ }
+
+ final OpenxBidExt openxBidExt = parseOpenxBidExt(ext);
+ final Integer buyerId = parseStringToInt(openxBidExt.getBuyerId());
+ final Integer dspId = parseStringToInt(openxBidExt.getDspId());
+ final Integer brandId = parseStringToInt(openxBidExt.getBrandId());
+
+ if (buyerId == null && dspId == null && brandId == null) {
+ return ext;
+ }
+
+ final ExtBidPrebidMeta meta = ExtBidPrebidMeta.builder()
+ .networkId(dspId)
+ .advertiserId(buyerId)
+ .brandId(brandId)
+ .build();
+
+ final ExtBidPrebid extBidPrebid = ExtBidPrebid.builder().meta(meta).build();
+
+ ext.set(PREBID_EXT, mapper.mapper().valueToTree(extBidPrebid));
+
+ return ext;
+ }
+
+ private OpenxBidExt parseOpenxBidExt(ObjectNode ext) {
+ try {
+ return mapper.mapper().convertValue(ext, OpenxBidExt.class);
+ } catch (IllegalArgumentException e) {
+ return OpenxBidExt.builder().build();
+ }
+ }
+
+ private static Integer parseStringToInt(String value) {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
}
diff --git a/src/main/java/org/prebid/server/bidder/openx/proto/OpenxBidExt.java b/src/main/java/org/prebid/server/bidder/openx/proto/OpenxBidExt.java
new file mode 100644
index 00000000000..00305cfb64b
--- /dev/null
+++ b/src/main/java/org/prebid/server/bidder/openx/proto/OpenxBidExt.java
@@ -0,0 +1,15 @@
+package org.prebid.server.bidder.openx.proto;
+
+import lombok.Builder;
+import lombok.Value;
+
+@Builder
+@Value
+public class OpenxBidExt {
+
+ String dspId;
+
+ String buyerId;
+
+ String brandId;
+}
diff --git a/src/test/java/org/prebid/server/bidder/openx/OpenxBidderTest.java b/src/test/java/org/prebid/server/bidder/openx/OpenxBidderTest.java
index 461f300c051..fab9bedceb7 100644
--- a/src/test/java/org/prebid/server/bidder/openx/OpenxBidderTest.java
+++ b/src/test/java/org/prebid/server/bidder/openx/OpenxBidderTest.java
@@ -23,6 +23,7 @@
import org.prebid.server.bidder.model.HttpRequest;
import org.prebid.server.bidder.model.HttpResponse;
import org.prebid.server.bidder.model.Result;
+import org.prebid.server.bidder.openx.proto.OpenxBidExt;
import org.prebid.server.bidder.openx.proto.OpenxBidResponse;
import org.prebid.server.bidder.openx.proto.OpenxBidResponseExt;
import org.prebid.server.bidder.openx.proto.OpenxRequestExt;
@@ -983,6 +984,155 @@ public void makeBidsShouldReturnResultContainingEmptyValueAndErrorsWhenSeatBidEm
.containsOnly(Collections.emptyList(), Collections.emptyList());
}
+ @Test
+ public void makeBidShouldReturnBidWithExtPrebidMetaContainingAllFieldsFromBidExt() throws JsonProcessingException {
+ // given
+ final ObjectNode bidExt = mapper.valueToTree(OpenxBidExt.builder()
+ .dspId("1")
+ .buyerId("2")
+ .brandId("3")
+ .build());
+ final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString(
+ BidResponse.builder()
+ .seatbid(singletonList(SeatBid.builder()
+ .bid(List.of(
+ Bid.builder()
+ .w(200)
+ .h(150)
+ .price(BigDecimal.ONE)
+ .impid("impId1")
+ .dealid("dealid")
+ .adm("This is an Ad
")
+ .ext(bidExt)
+ .build()))
+ .build()))
+ .build()));
+
+ final BidRequest bidRequest = BidRequest.builder()
+ .id("bidRequestId")
+ .imp(List.of(
+ Imp.builder()
+ .id("impId1")
+ .banner(Banner.builder().build())
+ .build()))
+ .build();
+
+ // when
+ final CompositeBidderResponse result = target.makeBidderResponse(httpCall, bidRequest);
+
+ // then
+ final ObjectNode expectedExtWithBidMeta = mapper.createObjectNode()
+ .put("dsp_id", "1")
+ .put("buyer_id", "2")
+ .put("brand_id", "3")
+ .set("prebid", mapper.createObjectNode()
+ .set("meta", mapper.createObjectNode()
+ .put("advertiserId", 2)
+ .put("brandId", 3)
+ .put("networkId", 1)));
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getBids()).hasSize(1)
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getExt)
+ .containsExactly(expectedExtWithBidMeta);
+ }
+
+ @Test
+ public void makeBidShouldReturnBidWithExtPrebidMetaContainingBrandIdFieldOnly() throws JsonProcessingException {
+ // given
+ final ObjectNode bidExt = mapper.valueToTree(OpenxBidExt.builder()
+ .brandId("4")
+ .build());
+ final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString(
+ BidResponse.builder()
+ .seatbid(singletonList(SeatBid.builder()
+ .bid(List.of(
+ Bid.builder()
+ .w(200)
+ .h(150)
+ .price(BigDecimal.ONE)
+ .impid("impId1")
+ .dealid("dealid")
+ .adm("This is an Ad
")
+ .ext(bidExt)
+ .build()))
+ .build()))
+ .build()));
+
+ final BidRequest bidRequest = BidRequest.builder()
+ .id("bidRequestId")
+ .imp(List.of(
+ Imp.builder()
+ .id("impId1")
+ .banner(Banner.builder().build())
+ .build()))
+ .build();
+
+ // when
+ final CompositeBidderResponse result = target.makeBidderResponse(httpCall, bidRequest);
+
+ // then
+ final ObjectNode expectedExtWithBidMeta = mapper.createObjectNode()
+ .put("brand_id", "4")
+ .set("prebid", mapper.createObjectNode()
+ .set("meta", mapper.createObjectNode()
+ .put("brandId", 4)));
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getBids()).hasSize(1)
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getExt)
+ .containsExactly(expectedExtWithBidMeta);
+ }
+
+ @Test
+ public void makeBidShouldReturnBidWithExtPrebidMetaNotContainingFieldsWithInvalidValues()
+ throws JsonProcessingException {
+ // given
+ final ObjectNode bidExt = mapper.valueToTree(OpenxBidExt.builder()
+ .dspId("abc")
+ .buyerId("xyz")
+ .brandId("cba")
+ .build());
+ final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString(
+ BidResponse.builder()
+ .seatbid(singletonList(SeatBid.builder()
+ .bid(List.of(
+ Bid.builder()
+ .w(200)
+ .h(150)
+ .price(BigDecimal.ONE)
+ .impid("impId1")
+ .dealid("dealid")
+ .adm("This is an Ad
")
+ .ext(bidExt)
+ .build()))
+ .build()))
+ .build()));
+
+ final BidRequest bidRequest = BidRequest.builder()
+ .id("bidRequestId")
+ .imp(List.of(
+ Imp.builder()
+ .id("impId1")
+ .banner(Banner.builder().build())
+ .build()))
+ .build();
+
+ // when
+ final CompositeBidderResponse result = target.makeBidderResponse(httpCall, bidRequest);
+
+ // then
+ final ObjectNode expectedExtWithBidMeta = mapper.createObjectNode()
+ .put("dsp_id", "abc")
+ .put("buyer_id", "xyz")
+ .put("brand_id", "cba");
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getBids()).hasSize(1)
+ .extracting(BidderBid::getBid)
+ .extracting(Bid::getExt)
+ .containsExactly(expectedExtWithBidMeta);
+ }
+
private static Map givenCustomParams(String key, Object values) {
return singletonMap(key, mapper.valueToTree(values));
}