From ce5bcdb1f397a0a9868c91a6ebdff137f606251c Mon Sep 17 00:00:00 2001 From: Marcin Komorski Date: Wed, 14 Aug 2024 15:43:53 +0200 Subject: [PATCH 1/9] initial commit --- modules/bidResponseFilter/index.js | 37 +++++++ test/spec/modules/bidResponseFilter_spec.js | 109 ++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 modules/bidResponseFilter/index.js create mode 100644 test/spec/modules/bidResponseFilter_spec.js diff --git a/modules/bidResponseFilter/index.js b/modules/bidResponseFilter/index.js new file mode 100644 index 00000000000..5a3b7b37192 --- /dev/null +++ b/modules/bidResponseFilter/index.js @@ -0,0 +1,37 @@ +import { config } from '../../src/config.js'; +import { getHook } from '../../src/hook.js'; + +const MODULE_NAME = 'bidResponseFilter'; +export const PUBLISHER_FILTER_REJECTION_REASON = 'PUBLISHER_FILTER_REJECTION_REASON'; +export const BID_CATEGORY_REJECTION_REASON = 'BID_CATEGORY_REJECTION_REASON'; +export const BID_ADV_DOMAINS_REJECTION_REASON = 'BID_ADV_DOMAINS_REJECTION_REASON'; +export const BID_ATTR_REJECTION_REASON = 'BID_ATTR_REJECTION_REASON'; + +function init() { + getHook('addBidResponse').before(addBidResponseHook); +}; + +export function addBidResponseHook(next, adUnitCode, bid, reject) { + const filterFn = config.getConfig(`${MODULE_NAME}.filterFn`) + const filter = typeof filterFn == 'function' ? filterFn : () => true; + const { bcat = [], badv = [], battr = [] } = config.getAnyConfig('ortb2') || {}; + const { primaryCatId, advertiserDomains = [], attr } = bid.meta || {}; + + // checking if bid fulfills validation rule set by publisher + if (!filter(bid)) { + reject(PUBLISHER_FILTER_REJECTION_REASON); + } else { + // checking if bid fulfills ortb2 fields rules + if (bcat.includes(primaryCatId)) { + reject(BID_CATEGORY_REJECTION_REASON); + } else if (badv.some(domain => advertiserDomains.includes(domain))) { + reject(BID_ADV_DOMAINS_REJECTION_REASON); + } else if (battr.includes(attr)) { + reject(BID_ATTR_REJECTION_REASON); + } else { + return next(adUnitCode, bid, reject); + } + } +} + +init(); diff --git a/test/spec/modules/bidResponseFilter_spec.js b/test/spec/modules/bidResponseFilter_spec.js new file mode 100644 index 00000000000..c27c3452459 --- /dev/null +++ b/test/spec/modules/bidResponseFilter_spec.js @@ -0,0 +1,109 @@ +import { BID_ADV_DOMAINS_REJECTION_REASON, BID_ATTR_REJECTION_REASON, BID_CATEGORY_REJECTION_REASON, PUBLISHER_FILTER_REJECTION_REASON, addBidResponseHook } from '../../../modules/bidResponseFilter'; +import { config } from '../../../src/config'; + +describe('bidResponseFilter', () => { + afterEach(() => { + config.resetConfig(); + }); + + it('should reject the bid after failed publisher rule validation', () => { + const reject = sinon.stub(); + const bid = { + meta: {} + }; + const filterFn = (bid) => { + return bid.meta.hasOwnProperty('expectedFieldName'); + }; + config.setConfig({bidResponseFilter: {filterFn}}); + + addBidResponseHook(() => {}, 'adcode', bid, reject); + sinon.assert.calledWith(reject, PUBLISHER_FILTER_REJECTION_REASON); + }); + + it('should pass the bid after successful publisher rule validation', () => { + const call = sinon.stub(); + const bid = { + meta: { + expectedFieldName: ['domain1.com', 'domain2.com'] + } + }; + const filterFn = (bid) => { + return bid.meta.hasOwnProperty('expectedFieldName'); + }; + config.setConfig({bidResponseFilter: {filterFn}}); + + addBidResponseHook(call, 'adcode', bid, () => {}); + sinon.assert.calledOnce(call); + }); + + it('should pass the bid after successful ortb2 rules validation', () => { + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['domain1.com', 'domain2.com'], + primaryCatId: 'EXAMPLE-CAT-ID', + attr: 'attr' + } + }; + config.setConfig({ortb2: { + badv: [], bcat: ['BANNED_CAT1', 'BANNED_CAT2'], battr: 'BANNED_ATTR' + }}); + + addBidResponseHook(call, 'adcode', bid, () => {}); + sinon.assert.calledOnce(call); + }); + + it('should reject the bid after failed ortb2 cat rule validation', () => { + const reject = sinon.stub(); + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['domain1.com', 'domain2.com'], + primaryCatId: 'BANNED_CAT1', + attr: 'attr' + } + }; + config.setConfig({ortb2: { + badv: [], bcat: ['BANNED_CAT1', 'BANNED_CAT2'], battr: 'BANNED_ATTR' + }}); + + addBidResponseHook(call, 'adcode', bid, reject); + sinon.assert.calledWith(reject, BID_CATEGORY_REJECTION_REASON); + }); + + it('should reject the bid after failed ortb2 adv domains rule validation', () => { + const rejection = sinon.stub(); + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['domain1.com', 'domain2.com'], + primaryCatId: 'VALID_CAT', + attr: 'attr' + } + }; + config.setConfig({ortb2: { + badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'], battr: 'BANNED_ATTR' + }}); + + addBidResponseHook(call, 'adcode', bid, rejection); + sinon.assert.calledWith(rejection, BID_ADV_DOMAINS_REJECTION_REASON); + }); + + it('should reject the bid after failed ortb2 attr rule validation', () => { + const reject = sinon.stub(); + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['validdomain1.com', 'validdomain2.com'], + primaryCatId: 'VALID_CAT', + attr: 'BANNED_ATTR' + } + }; + config.setConfig({ortb2: { + badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'], battr: 'BANNED_ATTR' + }}); + + addBidResponseHook(call, 'adcode', bid, reject); + sinon.assert.calledWith(reject, BID_ATTR_REJECTION_REASON); + }); +}) From a89fcbd320e76f4ce9591fd26ef4c1834720d04c Mon Sep 17 00:00:00 2001 From: Marcin Komorski Date: Wed, 21 Aug 2024 20:13:11 +0200 Subject: [PATCH 2/9] update --- libraries/ortbConverter/processors/default.js | 7 ++ modules/bidResponseFilter/index.js | 36 +++++----- test/spec/modules/bidResponseFilter_spec.js | 70 +++++++++++-------- 3 files changed, 65 insertions(+), 48 deletions(-) diff --git a/libraries/ortbConverter/processors/default.js b/libraries/ortbConverter/processors/default.js index d92a51daba2..1159369042b 100644 --- a/libraries/ortbConverter/processors/default.js +++ b/libraries/ortbConverter/processors/default.js @@ -100,6 +100,13 @@ export const DEFAULT_PROCESSORS = { if (bid.ext?.dsa) { bidResponse.meta.dsa = bid.ext.dsa; } + if (bid.cat) { + bidResponse.meta.secondaryCatIds = bid.cat; + bidResponse.meta.primaryCatId = bid.cat[0]; + } + if (bid.attr) { + bidResponse.meta.attr = bid.attr; + } } } } diff --git a/modules/bidResponseFilter/index.js b/modules/bidResponseFilter/index.js index 5a3b7b37192..f8c0f9b1a87 100644 --- a/modules/bidResponseFilter/index.js +++ b/modules/bidResponseFilter/index.js @@ -1,7 +1,7 @@ import { config } from '../../src/config.js'; import { getHook } from '../../src/hook.js'; -const MODULE_NAME = 'bidResponseFilter'; +export const MODULE_NAME = 'bidResponseFilter'; export const PUBLISHER_FILTER_REJECTION_REASON = 'PUBLISHER_FILTER_REJECTION_REASON'; export const BID_CATEGORY_REJECTION_REASON = 'BID_CATEGORY_REJECTION_REASON'; export const BID_ADV_DOMAINS_REJECTION_REASON = 'BID_ADV_DOMAINS_REJECTION_REASON'; @@ -12,25 +12,27 @@ function init() { }; export function addBidResponseHook(next, adUnitCode, bid, reject) { - const filterFn = config.getConfig(`${MODULE_NAME}.filterFn`) - const filter = typeof filterFn == 'function' ? filterFn : () => true; const { bcat = [], badv = [], battr = [] } = config.getAnyConfig('ortb2') || {}; - const { primaryCatId, advertiserDomains = [], attr } = bid.meta || {}; + const moduleConfig = config.getConfig(MODULE_NAME); + + const catConfig = moduleConfig?.cat || { enforce: true, block_unknown_cat: true }; + const advConfig = moduleConfig?.adv || { enforce: true, block_unknown_adomain: true }; + const attrConfig = moduleConfig?.attr || { enforce: true, block_unknown_attr: true }; - // checking if bid fulfills validation rule set by publisher - if (!filter(bid)) { - reject(PUBLISHER_FILTER_REJECTION_REASON); + const { primaryCatId, secondaryCatIds = [], advertiserDomains = [], attr: metaAttr } = bid.meta || {}; + + // checking if bid fulfills ortb2 fields rules + if ((catConfig.enforce && bcat.some(category => [primaryCatId, ...secondaryCatIds].includes(category))) || + (catConfig.block_unknown_cat && !primaryCatId)) { + reject(BID_CATEGORY_REJECTION_REASON); + } else if ((advConfig.enforce && badv.some(domain => advertiserDomains.includes(domain))) || + (advConfig.block_unknown_adomain && !advertiserDomains.length)) { + reject(BID_ADV_DOMAINS_REJECTION_REASON); + } else if ((attrConfig.enforce && battr.includes(metaAttr)) || + (attrConfig.block_unknown_attr && !metaAttr)) { + reject(BID_ATTR_REJECTION_REASON); } else { - // checking if bid fulfills ortb2 fields rules - if (bcat.includes(primaryCatId)) { - reject(BID_CATEGORY_REJECTION_REASON); - } else if (badv.some(domain => advertiserDomains.includes(domain))) { - reject(BID_ADV_DOMAINS_REJECTION_REASON); - } else if (battr.includes(attr)) { - reject(BID_ATTR_REJECTION_REASON); - } else { - return next(adUnitCode, bid, reject); - } + return next(adUnitCode, bid, reject); } } diff --git a/test/spec/modules/bidResponseFilter_spec.js b/test/spec/modules/bidResponseFilter_spec.js index c27c3452459..772296a5342 100644 --- a/test/spec/modules/bidResponseFilter_spec.js +++ b/test/spec/modules/bidResponseFilter_spec.js @@ -1,4 +1,4 @@ -import { BID_ADV_DOMAINS_REJECTION_REASON, BID_ATTR_REJECTION_REASON, BID_CATEGORY_REJECTION_REASON, PUBLISHER_FILTER_REJECTION_REASON, addBidResponseHook } from '../../../modules/bidResponseFilter'; +import { BID_ADV_DOMAINS_REJECTION_REASON, BID_ATTR_REJECTION_REASON, BID_CATEGORY_REJECTION_REASON, MODULE_NAME, PUBLISHER_FILTER_REJECTION_REASON, addBidResponseHook } from '../../../modules/bidResponseFilter'; import { config } from '../../../src/config'; describe('bidResponseFilter', () => { @@ -6,36 +6,6 @@ describe('bidResponseFilter', () => { config.resetConfig(); }); - it('should reject the bid after failed publisher rule validation', () => { - const reject = sinon.stub(); - const bid = { - meta: {} - }; - const filterFn = (bid) => { - return bid.meta.hasOwnProperty('expectedFieldName'); - }; - config.setConfig({bidResponseFilter: {filterFn}}); - - addBidResponseHook(() => {}, 'adcode', bid, reject); - sinon.assert.calledWith(reject, PUBLISHER_FILTER_REJECTION_REASON); - }); - - it('should pass the bid after successful publisher rule validation', () => { - const call = sinon.stub(); - const bid = { - meta: { - expectedFieldName: ['domain1.com', 'domain2.com'] - } - }; - const filterFn = (bid) => { - return bid.meta.hasOwnProperty('expectedFieldName'); - }; - config.setConfig({bidResponseFilter: {filterFn}}); - - addBidResponseHook(call, 'adcode', bid, () => {}); - sinon.assert.calledOnce(call); - }); - it('should pass the bid after successful ortb2 rules validation', () => { const call = sinon.stub(); const bid = { @@ -106,4 +76,42 @@ describe('bidResponseFilter', () => { addBidResponseHook(call, 'adcode', bid, reject); sinon.assert.calledWith(reject, BID_ATTR_REJECTION_REASON); }); + + it('should omit the validation if the flag is set to false', () => { + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['validdomain1.com', 'validdomain2.com'], + primaryCatId: 'BANNED_CAT1', + attr: 'valid_attr' + } + }; + config.setConfig({ortb2: { + badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'], battr: 'BANNED_ATTR' + }}); + + config.setConfig({ [MODULE_NAME]: { cat: { enforce: false }} }); + + addBidResponseHook(call, 'adcode', bid, () => {}); + sinon.assert.calledOnce(call); + }); + + it('should allow bid for unknown flag set to false', () => { + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['validdomain1.com', 'validdomain2.com'], + primaryCatId: undefined, + attr: 'valid_attr' + } + }; + config.setConfig({ortb2: { + badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'], battr: 'BANNED_ATTR' + }}); + + config.setConfig({ [MODULE_NAME]: { cat: { block_unknown_cat: false }} }); + + addBidResponseHook(call, 'adcode', bid, () => {}); + sinon.assert.calledOnce(call); + }); }) From ed8dda80c88af2ae5d03e7e7792c71aa413386ea Mon Sep 17 00:00:00 2001 From: Marcin Komorski Date: Tue, 27 Aug 2024 13:11:47 +0200 Subject: [PATCH 3/9] review changes --- modules/bidResponseFilter/index.js | 29 +++++++++---------- src/auctionIndex.js | 3 ++ test/spec/modules/bidResponseFilter_spec.js | 4 +-- .../spec/modules/dsp_genieeBidAdapter_spec.js | 4 ++- test/spec/unit/core/auctionIndex_spec.js | 28 ++++++++++++++++-- 5 files changed, 48 insertions(+), 20 deletions(-) diff --git a/modules/bidResponseFilter/index.js b/modules/bidResponseFilter/index.js index f8c0f9b1a87..3f79ea5ba57 100644 --- a/modules/bidResponseFilter/index.js +++ b/modules/bidResponseFilter/index.js @@ -2,10 +2,9 @@ import { config } from '../../src/config.js'; import { getHook } from '../../src/hook.js'; export const MODULE_NAME = 'bidResponseFilter'; -export const PUBLISHER_FILTER_REJECTION_REASON = 'PUBLISHER_FILTER_REJECTION_REASON'; -export const BID_CATEGORY_REJECTION_REASON = 'BID_CATEGORY_REJECTION_REASON'; -export const BID_ADV_DOMAINS_REJECTION_REASON = 'BID_ADV_DOMAINS_REJECTION_REASON'; -export const BID_ATTR_REJECTION_REASON = 'BID_ATTR_REJECTION_REASON'; +export const BID_CATEGORY_REJECTION_REASON = 'Category is not allowed'; +export const BID_ADV_DOMAINS_REJECTION_REASON = 'Adv domain is not allowed'; +export const BID_ATTR_REJECTION_REASON = 'Attr is not allowed'; function init() { getHook('addBidResponse').before(addBidResponseHook); @@ -14,22 +13,22 @@ function init() { export function addBidResponseHook(next, adUnitCode, bid, reject) { const { bcat = [], badv = [], battr = [] } = config.getAnyConfig('ortb2') || {}; const moduleConfig = config.getConfig(MODULE_NAME); - - const catConfig = moduleConfig?.cat || { enforce: true, block_unknown_cat: true }; - const advConfig = moduleConfig?.adv || { enforce: true, block_unknown_adomain: true }; - const attrConfig = moduleConfig?.attr || { enforce: true, block_unknown_attr: true }; + + const catConfig = {enforce: true, blockUnknown: true, ...(moduleConfig?.cat || {})}; + const advConfig = {enforce: true, blockUnknown: true, ...(moduleConfig?.adv || {})}; + const attrConfig = {enforce: true, blockUnknown: true, ...(moduleConfig?.attr || {})}; const { primaryCatId, secondaryCatIds = [], advertiserDomains = [], attr: metaAttr } = bid.meta || {}; - // checking if bid fulfills ortb2 fields rules - if ((catConfig.enforce && bcat.some(category => [primaryCatId, ...secondaryCatIds].includes(category))) || - (catConfig.block_unknown_cat && !primaryCatId)) { + // checking if bid fulfills ortb2 fields rules + if ((catConfig.enforce && bcat.some(category => [primaryCatId, ...secondaryCatIds].includes(category))) || + (catConfig.blockUnknown && !primaryCatId)) { reject(BID_CATEGORY_REJECTION_REASON); - } else if ((advConfig.enforce && badv.some(domain => advertiserDomains.includes(domain))) || - (advConfig.block_unknown_adomain && !advertiserDomains.length)) { + } else if ((advConfig.enforce && badv.some(domain => advertiserDomains.includes(domain))) || + (advConfig.blockUnknown && !advertiserDomains.length)) { reject(BID_ADV_DOMAINS_REJECTION_REASON); - } else if ((attrConfig.enforce && battr.includes(metaAttr)) || - (attrConfig.block_unknown_attr && !metaAttr)) { + } else if ((attrConfig.enforce && battr.includes(metaAttr)) || + (attrConfig.blockUnknown && !metaAttr)) { reject(BID_ATTR_REJECTION_REASON); } else { return next(adUnitCode, bid, reject); diff --git a/src/auctionIndex.js b/src/auctionIndex.js index afae2089518..3d56e1fe724 100644 --- a/src/auctionIndex.js +++ b/src/auctionIndex.js @@ -65,6 +65,9 @@ export function AuctionIndex(getAuctions) { .flatMap(ber => ber.bids) .find(br => br && br.bidId === requestId); } + }, + getOrtb2(bid) { + return this.getBidderRequest(bid)?.ortb2 || this.getAuction(bid)?.ortb2 } }); } diff --git a/test/spec/modules/bidResponseFilter_spec.js b/test/spec/modules/bidResponseFilter_spec.js index 772296a5342..13368cf69fb 100644 --- a/test/spec/modules/bidResponseFilter_spec.js +++ b/test/spec/modules/bidResponseFilter_spec.js @@ -90,7 +90,7 @@ describe('bidResponseFilter', () => { badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'], battr: 'BANNED_ATTR' }}); - config.setConfig({ [MODULE_NAME]: { cat: { enforce: false }} }); + config.setConfig({[MODULE_NAME]: {cat: {enforce: false}}}); addBidResponseHook(call, 'adcode', bid, () => {}); sinon.assert.calledOnce(call); @@ -109,7 +109,7 @@ describe('bidResponseFilter', () => { badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'], battr: 'BANNED_ATTR' }}); - config.setConfig({ [MODULE_NAME]: { cat: { block_unknown_cat: false }} }); + config.setConfig({[MODULE_NAME]: {cat: {blockUnknown: false}}}); addBidResponseHook(call, 'adcode', bid, () => {}); sinon.assert.calledOnce(call); diff --git a/test/spec/modules/dsp_genieeBidAdapter_spec.js b/test/spec/modules/dsp_genieeBidAdapter_spec.js index 94ec1011fbf..9a6f17a70b8 100644 --- a/test/spec/modules/dsp_genieeBidAdapter_spec.js +++ b/test/spec/modules/dsp_genieeBidAdapter_spec.js @@ -121,7 +121,9 @@ describe('Geniee adapter tests', () => { currency: 'JPY', mediaType: 'banner', meta: { - advertiserDomains: ['geniee.co.jp'] + advertiserDomains: ['geniee.co.jp'], + primaryCatId: 'IAB1', + secondaryCatIds: ['IAB1'] }, netRevenue: true, requestId: 'bid-id', diff --git a/test/spec/unit/core/auctionIndex_spec.js b/test/spec/unit/core/auctionIndex_spec.js index df29ed1a6cb..7993c7df94f 100644 --- a/test/spec/unit/core/auctionIndex_spec.js +++ b/test/spec/unit/core/auctionIndex_spec.js @@ -3,11 +3,12 @@ import {AuctionIndex} from '../../../../src/auctionIndex.js'; describe('auction index', () => { let index, auctions; - function mockAuction(id, adUnits, bidderRequests) { + function mockAuction(id, adUnits, bidderRequests, ortb2) { return { getAuctionId() { return id }, getAdUnits() { return adUnits; }, - getBidRequests() { return bidderRequests; } + getBidRequests() { return bidderRequests; }, + ortb2 } } @@ -126,4 +127,27 @@ describe('auction index', () => { }); }) }); + + describe('getOrtb2', () => { + let bidderRequests, adUnits = []; + beforeEach(() => { + bidderRequests = [ + {bidderRequestId: 'ber1', ortb2: {}, bids: [{bidId: 'b1', adUnitId: 'au1'}, {}]}, + {bidderRequestId: 'ber2', bids: [{bidId: 'b2', adUnitId: 'au2'}]} + ] + auctions = [ + mockAuction('a1', [adUnits[0]], [bidderRequests[0], {}]), + mockAuction('a2', [adUnits[1]], [bidderRequests[1]], {ortb2Field: true}) + ] + }); + it('should return ortb2 for bid if exists on bidder request', () => { + const ortb2 = index.getOrtb2({bidderRequestId: 'ber1'}); + expect(ortb2).to.be.a('object'); + }) + + it('should return ortb2 from auction if does not exist on bidder request', () => { + const ortb2 = index.getOrtb2({bidderRequestId: 'ber2', auctionId: 'a2'}); + expect(ortb2).to.be.deep.equals({ortb2Field: true}); + }) + }) }); From 5697f4ef3a8926c4510389c0a298341a7a88d0fc Mon Sep 17 00:00:00 2001 From: Marcin Komorski Date: Tue, 27 Aug 2024 16:21:24 +0200 Subject: [PATCH 4/9] + auction index --- modules/bidResponseFilter/index.js | 6 +- src/prebid.js | 13 ++++ test/spec/modules/bidResponseFilter_spec.js | 68 +++++++++++++-------- 3 files changed, 60 insertions(+), 27 deletions(-) diff --git a/modules/bidResponseFilter/index.js b/modules/bidResponseFilter/index.js index 3f79ea5ba57..3ace8108d2b 100644 --- a/modules/bidResponseFilter/index.js +++ b/modules/bidResponseFilter/index.js @@ -1,3 +1,4 @@ +import { auctionManager } from '../../src/auctionManager.js'; import { config } from '../../src/config.js'; import { getHook } from '../../src/hook.js'; @@ -10,8 +11,9 @@ function init() { getHook('addBidResponse').before(addBidResponseHook); }; -export function addBidResponseHook(next, adUnitCode, bid, reject) { - const { bcat = [], badv = [], battr = [] } = config.getAnyConfig('ortb2') || {}; +export function addBidResponseHook(next, adUnitCode, bid, reject, index = auctionManager.index) { + const {bcat = [], badv = []} = index.getOrtb2(bid) || {}; + const battr = index.getBidRequest(bid)?.ortb2Imp[bid.mediaType]?.battr || index.getAdUnit(bid)?.ortb2Imp[bid.mediaType]?.battr || []; const moduleConfig = config.getConfig(MODULE_NAME); const catConfig = {enforce: true, blockUnknown: true, ...(moduleConfig?.cat || {})}; diff --git a/src/prebid.js b/src/prebid.js index c92ab8f5a89..e843467bfed 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -100,6 +100,16 @@ function validateSizes(sizes, targLength) { return cleanSizes; } +function setBattrForAdUnit(adUnit, mediaType) { + const battr = adUnit.ortb2Imp?.[mediaType]?.battr || adUnit.mediaType?.[mediaType]?.battr; + if (adUnit.ortb2Imp?.[mediaType] && battr) { + adUnit.ortb2Imp[mediaType].battr = battr; + } + if (adUnit.mediaTypes?.[mediaType] && battr) { + adUnit.mediaTypes[mediaType].battr = battr; + } +} + function validateBannerMediaType(adUnit) { const validatedAdUnit = deepClone(adUnit); const banner = validatedAdUnit.mediaTypes.banner; @@ -112,6 +122,7 @@ function validateBannerMediaType(adUnit) { logError('Detected a mediaTypes.banner object without a proper sizes field. Please ensure the sizes are listed like: [[300, 250], ...]. Removing invalid mediaTypes.banner object from request.'); delete validatedAdUnit.mediaTypes.banner } + setBattrForAdUnit(validatedAdUnit, 'banner'); return validatedAdUnit; } @@ -135,6 +146,7 @@ function validateVideoMediaType(adUnit) { } } validateOrtbVideoFields(validatedAdUnit); + setBattrForAdUnit(validatedAdUnit, 'video'); return validatedAdUnit; } @@ -184,6 +196,7 @@ function validateNativeMediaType(adUnit) { logError('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.'); delete validatedAdUnit.mediaTypes.native.icon.sizes; } + setBattrForAdUnit(validatedAdUnit, 'native'); return validatedAdUnit; } diff --git a/test/spec/modules/bidResponseFilter_spec.js b/test/spec/modules/bidResponseFilter_spec.js index 13368cf69fb..3990cd3feb3 100644 --- a/test/spec/modules/bidResponseFilter_spec.js +++ b/test/spec/modules/bidResponseFilter_spec.js @@ -2,12 +2,22 @@ import { BID_ADV_DOMAINS_REJECTION_REASON, BID_ATTR_REJECTION_REASON, BID_CATEGO import { config } from '../../../src/config'; describe('bidResponseFilter', () => { - afterEach(() => { + let mockAuctionIndex + beforeEach(() => { config.resetConfig(); + mockAuctionIndex = { + getBidRequest: () => {}, + getAdUnit: () => {} + }; }); it('should pass the bid after successful ortb2 rules validation', () => { const call = sinon.stub(); + + mockAuctionIndex.getOrtb2 = () => ({ + badv: [], bcat: ['BANNED_CAT1', 'BANNED_CAT2'] + }); + const bid = { meta: { advertiserDomains: ['domain1.com', 'domain2.com'], @@ -15,11 +25,8 @@ describe('bidResponseFilter', () => { attr: 'attr' } }; - config.setConfig({ortb2: { - badv: [], bcat: ['BANNED_CAT1', 'BANNED_CAT2'], battr: 'BANNED_ATTR' - }}); - addBidResponseHook(call, 'adcode', bid, () => {}); + addBidResponseHook(call, 'adcode', bid, () => {}, mockAuctionIndex); sinon.assert.calledOnce(call); }); @@ -33,11 +40,11 @@ describe('bidResponseFilter', () => { attr: 'attr' } }; - config.setConfig({ortb2: { - badv: [], bcat: ['BANNED_CAT1', 'BANNED_CAT2'], battr: 'BANNED_ATTR' - }}); + mockAuctionIndex.getOrtb2 = () => ({ + badv: [], bcat: ['BANNED_CAT1', 'BANNED_CAT2'] + }); - addBidResponseHook(call, 'adcode', bid, reject); + addBidResponseHook(call, 'adcode', bid, reject, mockAuctionIndex); sinon.assert.calledWith(reject, BID_CATEGORY_REJECTION_REASON); }); @@ -51,11 +58,11 @@ describe('bidResponseFilter', () => { attr: 'attr' } }; - config.setConfig({ortb2: { - badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'], battr: 'BANNED_ATTR' - }}); + mockAuctionIndex.getOrtb2 = () => ({ + badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'] + }); - addBidResponseHook(call, 'adcode', bid, rejection); + addBidResponseHook(call, 'adcode', bid, rejection, mockAuctionIndex); sinon.assert.calledWith(rejection, BID_ADV_DOMAINS_REJECTION_REASON); }); @@ -67,13 +74,22 @@ describe('bidResponseFilter', () => { advertiserDomains: ['validdomain1.com', 'validdomain2.com'], primaryCatId: 'VALID_CAT', attr: 'BANNED_ATTR' - } + }, + mediaType: 'video' }; - config.setConfig({ortb2: { - badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'], battr: 'BANNED_ATTR' - }}); + mockAuctionIndex.getOrtb2 = () => ({ + badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'] + }); - addBidResponseHook(call, 'adcode', bid, reject); + mockAuctionIndex.getBidRequest = () => ({ + ortb2Imp: { + video: { + battr: 'BANNED_ATTR' + } + } + }) + + addBidResponseHook(call, 'adcode', bid, reject, mockAuctionIndex); sinon.assert.calledWith(reject, BID_ATTR_REJECTION_REASON); }); @@ -86,13 +102,14 @@ describe('bidResponseFilter', () => { attr: 'valid_attr' } }; - config.setConfig({ortb2: { - badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'], battr: 'BANNED_ATTR' - }}); + + mockAuctionIndex.getOrtb2 = () => ({ + badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'] + }); config.setConfig({[MODULE_NAME]: {cat: {enforce: false}}}); - addBidResponseHook(call, 'adcode', bid, () => {}); + addBidResponseHook(call, 'adcode', bid, () => {}, mockAuctionIndex); sinon.assert.calledOnce(call); }); @@ -105,9 +122,10 @@ describe('bidResponseFilter', () => { attr: 'valid_attr' } }; - config.setConfig({ortb2: { - badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'], battr: 'BANNED_ATTR' - }}); + + mockAuctionIndex.getOrtb2 = () => ({ + badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'] + }); config.setConfig({[MODULE_NAME]: {cat: {blockUnknown: false}}}); From 585bb461804e4e06f6895dc6a68835808943de2f Mon Sep 17 00:00:00 2001 From: Marcin Komorski Date: Mon, 2 Sep 2024 12:40:43 +0200 Subject: [PATCH 5/9] unit tests to ortb converter --- test/spec/ortbConverter/banner_spec.js | 10 ++++++++++ test/spec/ortbConverter/native_spec.js | 10 ++++++++++ test/spec/ortbConverter/video_spec.js | 10 ++++++++++ 3 files changed, 30 insertions(+) diff --git a/test/spec/ortbConverter/banner_spec.js b/test/spec/ortbConverter/banner_spec.js index 0f6686283a1..a54dbcd0847 100644 --- a/test/spec/ortbConverter/banner_spec.js +++ b/test/spec/ortbConverter/banner_spec.js @@ -126,6 +126,16 @@ describe('pbjs -> ortb banner conversion', () => { expect(imp.banner.someParam).to.eql('someValue'); }); + it('should keep ortb2Imp.banner.battr', () => { + const imp = { + banner: { + battr: 'battr' + } + }; + fillBannerImp(imp, {mediaTypes: {banner: {sizes: [1, 2]}}}, {}); + expect(imp.banner.battr).to.eql('battr'); + }); + it('does nothing if context.mediaType is set but is not BANNER', () => { const imp = {}; fillBannerImp(imp, {mediaTypes: {banner: {sizes: [1, 2]}}}, {mediaType: VIDEO}); diff --git a/test/spec/ortbConverter/native_spec.js b/test/spec/ortbConverter/native_spec.js index 56c733817cd..8ff1f9254fb 100644 --- a/test/spec/ortbConverter/native_spec.js +++ b/test/spec/ortbConverter/native_spec.js @@ -46,6 +46,16 @@ describe('pbjs -> ortb native requests', () => { expect(imp.native.something).to.eql('orother') }); + it('should keep ortb2Imp.native.battr', () => { + const imp = { + native: { + battr: 'battr' + } + }; + fillNativeImp(imp, {mediaTypes: {native: {sizes: [1, 2]}}}, {}); + expect(imp.native.battr).to.eql('battr'); + }); + it('should do nothing if there are no assets', () => { const imp = {}; fillNativeImp(imp, {nativeOrtbRequest: {assets: []}}, {}); diff --git a/test/spec/ortbConverter/video_spec.js b/test/spec/ortbConverter/video_spec.js index ab4034bb60a..9a3675beb6d 100644 --- a/test/spec/ortbConverter/video_spec.js +++ b/test/spec/ortbConverter/video_spec.js @@ -122,6 +122,16 @@ describe('pbjs -> ortb video conversion', () => { expect(imp.video.someParam).to.eql('someValue'); }); + it('should keep ortb2Imp.video.battr', () => { + const imp = { + video: { + battr: 'battr' + } + }; + fillVideoImp(imp, {mediaTypes: {video: {sizes: [1, 2]}}}, {}); + expect(imp.video.battr).to.eql('battr'); + }); + it('does nothing is context.mediaType is set but is not VIDEO', () => { const imp = {}; fillVideoImp(imp, {mediaTypes: {video: {playerSize: [[1, 2]]}}}, {mediaType: BANNER}); From 66bc55ab9036c616bcba399052f4bcec929c4834 Mon Sep 17 00:00:00 2001 From: Marcin Komorski Date: Tue, 10 Sep 2024 12:51:06 +0200 Subject: [PATCH 6/9] review changes --- libraries/ortbConverter/processors/default.js | 2 +- src/auctionIndex.js | 2 +- test/spec/unit/core/auctionIndex_spec.js | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/libraries/ortbConverter/processors/default.js b/libraries/ortbConverter/processors/default.js index 1159369042b..9d916b87172 100644 --- a/libraries/ortbConverter/processors/default.js +++ b/libraries/ortbConverter/processors/default.js @@ -101,8 +101,8 @@ export const DEFAULT_PROCESSORS = { bidResponse.meta.dsa = bid.ext.dsa; } if (bid.cat) { - bidResponse.meta.secondaryCatIds = bid.cat; bidResponse.meta.primaryCatId = bid.cat[0]; + bidResponse.meta.secondaryCatIds = bid.cat.slice(1); } if (bid.attr) { bidResponse.meta.attr = bid.attr; diff --git a/src/auctionIndex.js b/src/auctionIndex.js index 3d56e1fe724..d0b8355352a 100644 --- a/src/auctionIndex.js +++ b/src/auctionIndex.js @@ -67,7 +67,7 @@ export function AuctionIndex(getAuctions) { } }, getOrtb2(bid) { - return this.getBidderRequest(bid)?.ortb2 || this.getAuction(bid)?.ortb2 + return this.getBidderRequest(bid)?.ortb2 || this.getAuction(bid)?.getFPD()?.global?.ortb2 } }); } diff --git a/test/spec/unit/core/auctionIndex_spec.js b/test/spec/unit/core/auctionIndex_spec.js index 7993c7df94f..cc93e66adcf 100644 --- a/test/spec/unit/core/auctionIndex_spec.js +++ b/test/spec/unit/core/auctionIndex_spec.js @@ -8,7 +8,9 @@ describe('auction index', () => { getAuctionId() { return id }, getAdUnits() { return adUnits; }, getBidRequests() { return bidderRequests; }, - ortb2 + getFPD() { + return { global: { ortb2 } } + } } } From 8366048e40426b58e1aadb684f4d544ef35c2094 Mon Sep 17 00:00:00 2001 From: Marcin Komorski Date: Wed, 11 Sep 2024 15:55:56 +0200 Subject: [PATCH 7/9] battr setting --- src/prebid.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index e843467bfed..6e0b7c30bc2 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -101,13 +101,17 @@ function validateSizes(sizes, targLength) { } function setBattrForAdUnit(adUnit, mediaType) { - const battr = adUnit.ortb2Imp?.[mediaType]?.battr || adUnit.mediaType?.[mediaType]?.battr; - if (adUnit.ortb2Imp?.[mediaType] && battr) { - adUnit.ortb2Imp[mediaType].battr = battr; - } - if (adUnit.mediaTypes?.[mediaType] && battr) { - adUnit.mediaTypes[mediaType].battr = battr; + const ortb2Imp = adUnit.ortb2Imp || {}; + const mediaTypes = adUnit.mediaTypes || {}; + + if (ortb2Imp[mediaType]?.battr && mediaTypes[mediaType]?.battr && (ortb2Imp[mediaType]?.battr !== mediaTypes[mediaType]?.battr)) { + logWarn('battr field differ between ortb2Imp and mediaTypes'); } + + const battr = ortb2Imp[mediaType]?.battr || mediaTypes[mediaType]?.battr; + + adUnit.ortb2Imp = {...ortb2Imp, [mediaType]: {...(ortb2Imp[mediaType] || {}), battr}}; + adUnit.mediaTypes = {...mediaTypes, [mediaType]: {...(mediaTypes[mediaType] || {}), battr}}; } function validateBannerMediaType(adUnit) { From f90c448bfaf552d3f5a1dbbb33ca5c4591bd9f70 Mon Sep 17 00:00:00 2001 From: Marcin Komorski Date: Wed, 11 Sep 2024 22:49:59 +0200 Subject: [PATCH 8/9] improvements --- src/prebid.js | 12 ++-- .../spec/modules/dsp_genieeBidAdapter_spec.js | 2 +- test/spec/unit/pbjs_api_spec.js | 71 +++++++++++++++++++ 3 files changed, 79 insertions(+), 6 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index 6e0b7c30bc2..0c895cc3b50 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -100,18 +100,20 @@ function validateSizes(sizes, targLength) { return cleanSizes; } -function setBattrForAdUnit(adUnit, mediaType) { +export function setBattrForAdUnit(adUnit, mediaType) { const ortb2Imp = adUnit.ortb2Imp || {}; const mediaTypes = adUnit.mediaTypes || {}; if (ortb2Imp[mediaType]?.battr && mediaTypes[mediaType]?.battr && (ortb2Imp[mediaType]?.battr !== mediaTypes[mediaType]?.battr)) { - logWarn('battr field differ between ortb2Imp and mediaTypes'); + logWarn(`not equal: adUnit.ortb2Imp.${mediaType}.battr: ${ortb2Imp[mediaType].battr} and adUnit.mediaTypes.${mediaType}.battr: ${mediaTypes[mediaType].battr}`); } const battr = ortb2Imp[mediaType]?.battr || mediaTypes[mediaType]?.battr; - - adUnit.ortb2Imp = {...ortb2Imp, [mediaType]: {...(ortb2Imp[mediaType] || {}), battr}}; - adUnit.mediaTypes = {...mediaTypes, [mediaType]: {...(mediaTypes[mediaType] || {}), battr}}; + + if (battr !== undefined) { + deepSetValue(adUnit, `ortb2Imp.${mediaType}.battr`, battr); + deepSetValue(adUnit, `mediaTypes.${mediaType}.battr`, battr); + } } function validateBannerMediaType(adUnit) { diff --git a/test/spec/modules/dsp_genieeBidAdapter_spec.js b/test/spec/modules/dsp_genieeBidAdapter_spec.js index 9a6f17a70b8..6b2286a5fe5 100644 --- a/test/spec/modules/dsp_genieeBidAdapter_spec.js +++ b/test/spec/modules/dsp_genieeBidAdapter_spec.js @@ -123,7 +123,7 @@ describe('Geniee adapter tests', () => { meta: { advertiserDomains: ['geniee.co.jp'], primaryCatId: 'IAB1', - secondaryCatIds: ['IAB1'] + secondaryCatIds: [] }, netRevenue: true, requestId: 'bid-id', diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index de267b793b2..40c97b7710b 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -28,6 +28,7 @@ import {generateUUID} from '../../../src/utils.js'; import {getCreativeRenderer} from '../../../src/creativeRenderers.js'; import {BID_STATUS, EVENTS, GRANULARITY_OPTIONS, PB_LOCATOR, TARGETING_KEYS} from 'src/constants.js'; import {getBidToRender} from '../../../src/adRendering.js'; +import { setBattrForAdUnit } from '../../../src/prebid.js'; var assert = require('chai').assert; var expect = require('chai').expect; @@ -3768,4 +3769,74 @@ describe('Unit: Prebid Module', function () { expect(auctionManager.getBidsReceived().length).to.equal(0); }); }); + + describe('setBattrForAdUnit', () => { + it('should set copy battr to both places', () => { + const adUnit = { + ortb2Imp: { + video: { + battr: 'banned attribute' + } + }, + mediaTypes: { + video: {} + } + } + + setBattrForAdUnit(adUnit, 'video'); + + expect(adUnit.mediaTypes.video.battr).to.deep.equal('banned attribute'); + expect(adUnit.ortb2Imp.video.battr).to.deep.equal('banned attribute'); + + const adUnit2 = { + mediaTypes: { + video: { + battr: 'banned attribute' + } + }, + ortb2Imp: { + video: {} + } + } + + setBattrForAdUnit(adUnit2, 'video'); + + expect(adUnit2.ortb2Imp.video.battr).to.deep.equal('banned attribute'); + expect(adUnit2.mediaTypes.video.battr).to.deep.equal('banned attribute'); + }) + + it('should log warn if both are specified and differ from eachother', () => { + let spyLogWarn = sinon.spy(utils, 'logWarn'); + const adUnit = { + mediaTypes: { + native: { + battr: 'banned attribute' + } + }, + ortb2Imp: { + native: { + battr: 'banned attribute 2' + } + } + } + setBattrForAdUnit(adUnit, 'native'); + sinon.assert.calledOnce(spyLogWarn); + spyLogWarn.resetHistory(); + utils.logWarn.restore(); + }) + + it('should not copy for undefined battr', () => { + const adUnit = { + mediaTypes: { + native: {} + }, + ortb2Imp: { + native: {} + } + } + setBattrForAdUnit(adUnit, 'native'); + expect(adUnit.ortb2Imp.native).to.deep.equal({}); + expect(adUnit.mediaTypes.native).to.deep.equal({}); + }) + }) }); From fb9458c3325cec5222d7633e5856a36023b57eb1 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 12 Sep 2024 07:15:40 -0700 Subject: [PATCH 9/9] fix log message --- src/prebid.js | 4 ++-- test/spec/unit/pbjs_api_spec.js | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index 0c895cc3b50..a2b00ce7792 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -105,12 +105,12 @@ export function setBattrForAdUnit(adUnit, mediaType) { const mediaTypes = adUnit.mediaTypes || {}; if (ortb2Imp[mediaType]?.battr && mediaTypes[mediaType]?.battr && (ortb2Imp[mediaType]?.battr !== mediaTypes[mediaType]?.battr)) { - logWarn(`not equal: adUnit.ortb2Imp.${mediaType}.battr: ${ortb2Imp[mediaType].battr} and adUnit.mediaTypes.${mediaType}.battr: ${mediaTypes[mediaType].battr}`); + logWarn(`Ad unit ${adUnit.code} specifies conflicting ortb2Imp.${mediaType}.battr and mediaTypes.${mediaType}.battr, the latter will be ignored`, adUnit); } const battr = ortb2Imp[mediaType]?.battr || mediaTypes[mediaType]?.battr; - if (battr !== undefined) { + if (battr != null) { deepSetValue(adUnit, `ortb2Imp.${mediaType}.battr`, battr); deepSetValue(adUnit, `mediaTypes.${mediaType}.battr`, battr); } diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 40c97b7710b..f61f781cd96 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -3829,14 +3829,11 @@ describe('Unit: Prebid Module', function () { const adUnit = { mediaTypes: { native: {} - }, - ortb2Imp: { - native: {} } } setBattrForAdUnit(adUnit, 'native'); - expect(adUnit.ortb2Imp.native).to.deep.equal({}); expect(adUnit.mediaTypes.native).to.deep.equal({}); + expect(adUnit.mediaTypes.ortb2Imp).to.not.exist; }) }) });