From 7827258f7818b3b516bb053d136a9b95a4499ef6 Mon Sep 17 00:00:00 2001 From: Lyubomir Shishkov Date: Wed, 2 Oct 2024 00:07:46 +0300 Subject: [PATCH 1/5] 12238 - Azerion / Improve: does not properly support currency module * **Type:** Fix * **Scope:** improvedigitalBidAdapter * **Subject:** Bid floors are always converted to USD. * **Details:** * Adds `DEFAULT_CURRENCY` variable which is set to USD * Adds `convertBidFloorCurrency` function which in used to convert the bid floor when both `imp.bidfloor` and `imp.bidfloorcur` are present, and `imp.bidfloorcur` is not equal to the adapter's `DEFAULT_CURRENCY`; * **Breaks:** N/A --- modules/improvedigitalBidAdapter.js | 32 ++++++++++++++++- .../modules/improvedigitalBidAdapter_spec.js | 35 +++++++++++++++---- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index ddf13caa833..f3554878740 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -5,6 +5,7 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import {hasPurpose1Consent} from '../src/utils/gdpr.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {convertCurrency} from '../libraries/currencyUtils/currency.js'; /** * See https://github.com/prebid/Prebid.js/pull/8827 for details on linting exception * ImproveDigital only imports after winning a bid and only if the creative cannot reach top @@ -29,6 +30,7 @@ const BASIC_ADS_BASE_URL = 'https://ad.360yield-basic.com'; const PB_ENDPOINT = 'pb'; const EXTEND_URL = 'https://pbs.360yield.com/openrtb2/auction'; const IFRAME_SYNC_URL = 'https://hb.360yield.com/prebid-universal-creative/load-cookie.html'; +const DEFAULT_CURRENCY = 'USD'; const VIDEO_PARAMS = { DEFAULT_MIMES: ['video/mp4'] @@ -124,6 +126,30 @@ export const spec = { registerBidder(spec); +const convertBidFloorCurrency = (imp) => { + const omitBidFloor = () => { + delete imp.bidfloor; + delete imp.bidfloorcur; + logWarn('Cannot convert bid floor currency. Omitting bid floor from request.'); + }; + + try { + imp.bidfloor = parseFloat(convertCurrency( + imp.bidfloor, + imp.bidfloorcur.toUpperCase(), + DEFAULT_CURRENCY, + false, + ).toFixed(2)); + imp.bidfloorcur = DEFAULT_CURRENCY; + + if (isNaN(imp.bidfloor)) { + omitBidFloor(); + } + } catch (err) { + omitBidFloor(); + } +}; + export const CONVERTER = ortbConverter({ context: { ttl: CREATIVE_TTL, @@ -138,7 +164,11 @@ export const CONVERTER = ortbConverter({ imp.secure = Number(window.location.protocol === 'https:'); if (!imp.bidfloor && bidRequest.params.bidFloor) { imp.bidfloor = bidRequest.params.bidFloor; - imp.bidfloorcur = getBidIdParameter('bidFloorCur', bidRequest.params).toUpperCase() || 'USD' + imp.bidfloorcur = getBidIdParameter('bidFloorCur', bidRequest.params).toUpperCase() || DEFAULT_CURRENCY; + } + + if (imp.bidfloor && imp.bidfloorcur && imp.bidfloorcur.toUpperCase() !== DEFAULT_CURRENCY) { + convertBidFloorCurrency(imp); } const bidderParamsPath = context.extendMode ? 'ext.prebid.bidder.improvedigital' : 'ext.bidder'; const placementId = bidRequest.params.placementId; diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 5dc94f544ea..1429afab93b 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -16,6 +16,7 @@ import 'modules/schain.js'; import {decorateAdUnitsWithNativeParams} from '../../../src/native.js'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {hook} from '../../../src/hook.js'; +import * as prebidGlobal from 'src/prebidGlobal.js'; describe('Improve Digital Adapter Tests', function () { const METHOD = 'POST'; @@ -205,12 +206,18 @@ describe('Improve Digital Adapter Tests', function () { describe('buildRequests', function () { let getConfigStub = null; + let getGlobalStub = null; afterEach(function () { if (getConfigStub) { getConfigStub.restore(); getConfigStub = null; } + + if (getGlobalStub) { + getGlobalStub.restore(); + getGlobalStub = null; + } }); it('should make a well-formed request objects', function () { @@ -349,10 +356,17 @@ describe('Improve Digital Adapter Tests', function () { } }); - it('should add bid floor', function () { - const bidRequest = Object.assign({}, simpleBidRequest); - let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + it('should add bid floor correctly', function () { + getGlobalStub = sinon.stub(prebidGlobal, 'getGlobal').returns({ + convertCurrency: (cpm, from, to) => { + const conversionKeys = { 'EUR-USD': 1.75 }; + return cpm * conversionKeys[`${from}-${to}`]; + } + }); + const bidRequest = deepClone(simpleBidRequest); + // Floor price currency shouldn't be populated without a floor price + let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); expect(payload.imp[0].bidfloorcur).to.not.exist; // Default floor price currency @@ -361,18 +375,25 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.imp[0].bidfloor).to.equal(0.05); expect(payload.imp[0].bidfloorcur).to.equal('USD'); - // Floor price currency + // Floor price omitted when currency cannot be converted to default bid adapter currency + bidRequest.params.bidFloorCur = 'UAH'; + bidRequest.params.bidFloor = 0.05; + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].bidfloor).to.be.undefined; + expect(payload.imp[0].bidfloorcur).to.not.exist; + + // Floor price currency converted to default bid adapter currency bidRequest.params.bidFloorCur = 'eUR'; payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); - expect(payload.imp[0].bidfloor).to.equal(0.05); - expect(payload.imp[0].bidfloorcur).to.equal('EUR'); + expect(payload.imp[0].bidfloor).to.equal(0.09); + expect(payload.imp[0].bidfloorcur).to.equal('USD'); // getFloor defined -> use it over bidFloor let getFloorResponse = { currency: 'USD', floor: 3 }; bidRequest.getFloor = () => getFloorResponse; payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); expect(payload.imp[0].bidfloor).to.equal(3); - // expect(payload.imp[0].bidfloorcur).to.equal('USD'); + expect(payload.imp[0].bidfloorcur).to.equal('USD'); }); it('should add GDPR consent string', function () { From ff11c0cea3071985ae5e0a0e7cd93e4af9620cab Mon Sep 17 00:00:00 2001 From: Lyubomir Shishkov Date: Wed, 2 Oct 2024 00:15:29 +0300 Subject: [PATCH 2/5] restored accidentally discarded change from unit test expect --- test/spec/modules/improvedigitalBidAdapter_spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 1429afab93b..44932239d9f 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -1002,7 +1002,7 @@ describe('Improve Digital Adapter Tests', function () { width: 728, height: 90, ttl: 300, - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688,"keyValues":{"testKey":["testValue"]},"bidFloor":0.05,"bidFloorCur":"eUR","size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688,"keyValues":{"testKey":["testValue"]},"size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', creativeId: '510265', dealId: 320896, netRevenue: false, @@ -1025,7 +1025,7 @@ describe('Improve Digital Adapter Tests', function () { width: 300, height: 250, ttl: 300, - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688,"keyValues":{"testKey":["testValue"]},"bidFloor":0.05,"bidFloorCur":"eUR","size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"83c8d524-0955-4d0c-b558-4c9f3600e09b","cpm":1.9200543539802946,"currency":"EUR","width":300,"height":250,"creative_id":"479163","creativeId":"479163","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688,"keyValues":{"testKey":["testValue"]},"size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"83c8d524-0955-4d0c-b558-4c9f3600e09b","cpm":1.9200543539802946,"currency":"EUR","width":300,"height":250,"creative_id":"479163","creativeId":"479163","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', creativeId: '479163', dealId: 320896, netRevenue: false, From 638c39e5fac77496f28719544404af49fa81bca4 Mon Sep 17 00:00:00 2001 From: Lyubomir Shishkov Date: Wed, 2 Oct 2024 17:38:31 +0300 Subject: [PATCH 3/5] * Modifies behavior to pass bid floor as is when it cannot be converted to USD; * Removes rounding of bid floor when converting its currency to USD; --- modules/improvedigitalBidAdapter.js | 19 ++++++------------- .../modules/improvedigitalBidAdapter_spec.js | 8 ++++---- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index f3554878740..b5a6cf5f06b 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -127,26 +127,19 @@ export const spec = { registerBidder(spec); const convertBidFloorCurrency = (imp) => { - const omitBidFloor = () => { - delete imp.bidfloor; - delete imp.bidfloorcur; - logWarn('Cannot convert bid floor currency. Omitting bid floor from request.'); - }; - try { - imp.bidfloor = parseFloat(convertCurrency( + const bidFloor = convertCurrency( imp.bidfloor, imp.bidfloorcur.toUpperCase(), DEFAULT_CURRENCY, false, - ).toFixed(2)); - imp.bidfloorcur = DEFAULT_CURRENCY; - - if (isNaN(imp.bidfloor)) { - omitBidFloor(); + ); + if (typeof bidFloor === 'number' && !isNaN(bidFloor)) { + imp.bidfloor = bidFloor; + imp.bidfloorcur = DEFAULT_CURRENCY; } } catch (err) { - omitBidFloor(); + logWarn(`Failed to convert bid floor to ${DEFAULT_CURRENCY}. Passing floor price in its original currency.`, err); } }; diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 44932239d9f..844ded6abe9 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -375,17 +375,17 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.imp[0].bidfloor).to.equal(0.05); expect(payload.imp[0].bidfloorcur).to.equal('USD'); - // Floor price omitted when currency cannot be converted to default bid adapter currency + // Floor price sent as is when currency cannot be converted to default bid adapter currency bidRequest.params.bidFloorCur = 'UAH'; bidRequest.params.bidFloor = 0.05; payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); - expect(payload.imp[0].bidfloor).to.be.undefined; - expect(payload.imp[0].bidfloorcur).to.not.exist; + expect(payload.imp[0].bidfloor).to.equal(0.05); + expect(payload.imp[0].bidfloorcur).to.equal('UAH'); // Floor price currency converted to default bid adapter currency bidRequest.params.bidFloorCur = 'eUR'; payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); - expect(payload.imp[0].bidfloor).to.equal(0.09); + expect(payload.imp[0].bidfloor).to.equal(0.08750000000000001); expect(payload.imp[0].bidfloorcur).to.equal('USD'); // getFloor defined -> use it over bidFloor From 17f3fa6c0be57317dfa2478379ed6972e8f3bacf Mon Sep 17 00:00:00 2001 From: Lyubomir Shishkov Date: Fri, 11 Oct 2024 16:53:08 +0300 Subject: [PATCH 4/5] remove unnecessary uses of `toUpperCase()` --- modules/improvedigitalBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index b5a6cf5f06b..103fdb24e75 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -130,7 +130,7 @@ const convertBidFloorCurrency = (imp) => { try { const bidFloor = convertCurrency( imp.bidfloor, - imp.bidfloorcur.toUpperCase(), + imp.bidfloorcur, DEFAULT_CURRENCY, false, ); @@ -160,7 +160,7 @@ export const CONVERTER = ortbConverter({ imp.bidfloorcur = getBidIdParameter('bidFloorCur', bidRequest.params).toUpperCase() || DEFAULT_CURRENCY; } - if (imp.bidfloor && imp.bidfloorcur && imp.bidfloorcur.toUpperCase() !== DEFAULT_CURRENCY) { + if (imp.bidfloor && imp.bidfloorcur && imp.bidfloorcur !== DEFAULT_CURRENCY) { convertBidFloorCurrency(imp); } const bidderParamsPath = context.extendMode ? 'ext.prebid.bidder.improvedigital' : 'ext.bidder'; From 9e0a49c09a99e251de4877e681b70f4fbe214d9e Mon Sep 17 00:00:00 2001 From: Lyubomir Shishkov Date: Sat, 12 Oct 2024 00:26:22 +0300 Subject: [PATCH 5/5] * fix `convertCurrency` mock * remove redundant checks for type and NaN from `convertBidFloorCurrency` function --- modules/improvedigitalBidAdapter.js | 6 ++---- test/spec/modules/improvedigitalBidAdapter_spec.js | 6 +++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 103fdb24e75..c08edb3987d 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -134,10 +134,8 @@ const convertBidFloorCurrency = (imp) => { DEFAULT_CURRENCY, false, ); - if (typeof bidFloor === 'number' && !isNaN(bidFloor)) { - imp.bidfloor = bidFloor; - imp.bidfloorcur = DEFAULT_CURRENCY; - } + imp.bidfloor = bidFloor; + imp.bidfloorcur = DEFAULT_CURRENCY; } catch (err) { logWarn(`Failed to convert bid floor to ${DEFAULT_CURRENCY}. Passing floor price in its original currency.`, err); } diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 844ded6abe9..0c0789ced48 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -360,7 +360,11 @@ describe('Improve Digital Adapter Tests', function () { getGlobalStub = sinon.stub(prebidGlobal, 'getGlobal').returns({ convertCurrency: (cpm, from, to) => { const conversionKeys = { 'EUR-USD': 1.75 }; - return cpm * conversionKeys[`${from}-${to}`]; + const conversionRate = conversionKeys[`${from}-${to}`]; + if (!conversionRate) { + throw new Error(`No conversion rate found for ${from}-${to}`); + } + return cpm * conversionRate; } }); const bidRequest = deepClone(simpleBidRequest);