From 4a59ebfa0e6ba5d6258e730cb2c54066711444a0 Mon Sep 17 00:00:00 2001 From: Marcin Komorski Date: Mon, 4 Nov 2024 18:08:20 +0100 Subject: [PATCH 01/10] ortb2 <-> mediaTypes --- src/prebid.js | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index 975e4b4517b..400128f882d 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -41,7 +41,7 @@ import {enrichFPD} from './fpd/enrichment.js'; import {allConsent} from './consentHandler.js'; import {insertLocatorFrame, markBidAsRendered, renderAdDirect, renderIfDeferred} from './adRendering.js'; import {getHighestCpm} from './utils/reducers.js'; -import {fillVideoDefaults, validateOrtbVideoFields} from './video.js'; +import {ORTB_VIDEO_PARAMS, fillVideoDefaults, validateOrtbVideoFields} from './video.js'; const pbjsInstance = getGlobal(); const { triggerUserSyncs } = userSync; @@ -100,20 +100,36 @@ function validateSizes(sizes, targLength) { return cleanSizes; } -export function setBattrForAdUnit(adUnit, mediaType) { - const ortb2Imp = adUnit.ortb2Imp || {}; - const mediaTypes = adUnit.mediaTypes || {}; +// setting mediaTypes to ortb2 corresponding fields and opposite +export function syncOrtb2(adUnit, mediaType) { - if (ortb2Imp[mediaType]?.battr && mediaTypes[mediaType]?.battr && (ortb2Imp[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 ortb2Imp = deepAccess(adUnit, `ortb2Imp.${mediaType}`) || {}; + const mediaTypes = deepAccess(adUnit, `mediaTypes.${mediaType}`) || {}; - const battr = ortb2Imp[mediaType]?.battr || mediaTypes[mediaType]?.battr; + const fields = { + 'video': ORTB_VIDEO_PARAMS, + //@todo params for banner, native + }[mediaType]; - if (battr != null) { - deepSetValue(adUnit, `ortb2Imp.${mediaType}.battr`, battr); - deepSetValue(adUnit, `mediaTypes.${mediaType}.battr`, battr); + if (!fields || !(fields instanceof 'Map')) { + return; } + + fields.entries().forEach(([key, validator]) => { + const mediaTypesFieldValue = mediaTypes[key]; + const ortbFieldValue = ortb2Imp[key]; + + if (mediaTypesFieldValue == undefined && ortbFieldValue == undefined) { + return; + } else if (mediaTypesFieldValue == undefined) { + mediaTypes[key] = ortbFieldValue; + } else if (ortbFieldValue == undefined) { + ortb2Imp[key] = validator(mediaTypesFieldValue) ? mediaTypesFieldValue : undefined; + } else { + logWarn(`adUnit ${adUnit.code}: specifies conflicting ortb2Imp.${mediaType}.${key} and mediaTypes.${mediaType}.${key}, the latter will be ignored`, adUnit); + mediaTypes[key] = ortbFieldValue; + } + }) } function validateBannerMediaType(adUnit) { @@ -128,7 +144,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'); + syncOrtb2(validatedAdUnit, 'banner') return validatedAdUnit; } @@ -152,7 +168,7 @@ function validateVideoMediaType(adUnit) { } } validateOrtbVideoFields(validatedAdUnit); - setBattrForAdUnit(validatedAdUnit, 'video'); + syncOrtb2(validatedAdUnit, 'video'); return validatedAdUnit; } @@ -202,7 +218,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'); + syncOrtb2(validatedAdUnit, 'native'); return validatedAdUnit; } From 971a687cf519690e61cb1421e7c3da8d55fddc43 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Wed, 6 Nov 2024 10:40:30 +0100 Subject: [PATCH 02/10] native & banner params --- src/banner.js | 21 ++++++++++++++++++++ src/native.js | 14 +++++++++++++ src/prebid.js | 7 +++++-- test/spec/native_spec.js | 43 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 src/banner.js diff --git a/src/banner.js b/src/banner.js new file mode 100644 index 00000000000..29770692d09 --- /dev/null +++ b/src/banner.js @@ -0,0 +1,21 @@ +import { isArrayOfNums, isInteger, isStr } from "./utils"; + +/** + * List of OpenRTB 2.x banner object properties with simple validators. + * Not included: `format`, `ext` + * reference: https://github.com/InteractiveAdvertisingBureau/openrtb2.x/blob/main/2.6.md + */ +export const ORTB_BANNER_PARAMS = new Map([ + [ 'format', value => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'object') ], + [ 'w', isInteger ], + [ 'h', isInteger ], + [ 'btype', isArrayOfNums ], + [ 'battr', isArrayOfNums ], + [ 'pos', isInteger ], + [ 'mimes', value => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string') ], + [ 'topframe', value => [1, 0].includes(value) ], + [ 'expdir', isArrayOfNums ], + [ 'api', isArrayOfNums ], + [ 'id', isStr ], + [ 'vcm', value => [1, 0].includes(value) ] +]); \ No newline at end of file diff --git a/src/native.js b/src/native.js index a641beb71d5..782d316b551 100644 --- a/src/native.js +++ b/src/native.js @@ -3,10 +3,12 @@ import { deepClone, getDefinedParams, insertHtmlIntoIframe, isArray, + isArrayOfNums, isBoolean, isInteger, isNumber, isPlainObject, + isStr, logError, pick, triggerPixel @@ -18,6 +20,18 @@ import {NATIVE} from './mediaTypes.js'; import {getRenderingData} from './adRendering.js'; import {getCreativeRendererSource} from './creativeRenderers.js'; +/** + * List of OpenRTB 2.x native object properties with simple validators. + * Not included: `ext` + * reference: https://github.com/InteractiveAdvertisingBureau/openrtb2.x/blob/main/2.6.md + */ +export const ORTB_NATIVE_PARAMS = new Map([ + [ 'request', isStr ], + [ 'ver', isStr ], + [ 'api', isArrayOfNums ], + [ 'battr', isArrayOfNums ] +]); + /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid diff --git a/src/prebid.js b/src/prebid.js index 400128f882d..ea9731ee568 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -42,6 +42,8 @@ import {allConsent} from './consentHandler.js'; import {insertLocatorFrame, markBidAsRendered, renderAdDirect, renderIfDeferred} from './adRendering.js'; import {getHighestCpm} from './utils/reducers.js'; import {ORTB_VIDEO_PARAMS, fillVideoDefaults, validateOrtbVideoFields} from './video.js'; +import { ORTB_NATIVE_PARAMS } from './native.js'; +import { ORTB_BANNER_PARAMS } from './banner.js'; const pbjsInstance = getGlobal(); const { triggerUserSyncs } = userSync; @@ -108,10 +110,11 @@ export function syncOrtb2(adUnit, mediaType) { const fields = { 'video': ORTB_VIDEO_PARAMS, - //@todo params for banner, native + 'native': ORTB_NATIVE_PARAMS, + 'banner': ORTB_BANNER_PARAMS }[mediaType]; - if (!fields || !(fields instanceof 'Map')) { + if (!fields) { return; } diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 877306c1c04..3736b5768bd 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -23,6 +23,7 @@ import {auctionManager} from '../../src/auctionManager.js'; import {getRenderingData} from '../../src/adRendering.js'; import {getCreativeRendererSource} from '../../src/creativeRenderers.js'; import {deepClone, deepSetValue} from '../../src/utils.js'; +import { syncOrtb2 } from '../../src/prebid.js'; const utils = require('src/utils'); const bid = { @@ -1435,4 +1436,46 @@ describe('toOrtbNativeResponse', () => { } }) }) + + it('should sync mediaTypes and ortb2Imp properly', () => { + const adUnit = { + mediaTypes: { + native: { + api: [6], + battr: [3, 4], + fieldToOmit: 'omitted_value' + } + }, + ortb2Imp: { + native: { + request: '{payload: true}', + ver: '1.1', + battr: [1, 2] + } + } + }; + + const expected = { + mediaTypes: { + native: { + api: [6], + request: '{payload: true}', + ver: '1.1', + fieldToOmit: 'omitted_value', + battr: [1, 2], + } + }, + ortb2Imp: { + native: { + api: [6], + request: '{payload: true}', + ver: '1.1', + battr: [1, 2] + } + } + }; + + syncOrtb2(adUnit, 'native'); + expect(adUnit).to.deep.eql(expected); + }) }) From 43ee07f38277a3f43b50ab652cc80314524beb63 Mon Sep 17 00:00:00 2001 From: Marcin Komorski Date: Thu, 7 Nov 2024 11:14:44 +0100 Subject: [PATCH 03/10] syncOrtb2 --- src/banner.js | 6 +- src/prebid.js | 24 ++-- test/spec/banner_spec.js | 183 +++++++++++++++++++++++++++++ test/spec/native_spec.js | 188 ++++++++++++++++++++++++------ test/spec/unit/pbjs_api_spec.js | 67 ----------- test/spec/video_spec.js | 196 ++++++++++++++++++++++++++++++++ 6 files changed, 547 insertions(+), 117 deletions(-) create mode 100644 test/spec/banner_spec.js diff --git a/src/banner.js b/src/banner.js index 29770692d09..25da06b6669 100644 --- a/src/banner.js +++ b/src/banner.js @@ -1,8 +1,8 @@ -import { isArrayOfNums, isInteger, isStr } from "./utils"; +import { isArrayOfNums, isInteger, isStr } from './utils.js'; /** * List of OpenRTB 2.x banner object properties with simple validators. - * Not included: `format`, `ext` + * Not included: `ext` * reference: https://github.com/InteractiveAdvertisingBureau/openrtb2.x/blob/main/2.6.md */ export const ORTB_BANNER_PARAMS = new Map([ @@ -18,4 +18,4 @@ export const ORTB_BANNER_PARAMS = new Map([ [ 'api', isArrayOfNums ], [ 'id', isStr ], [ 'vcm', value => [1, 0].includes(value) ] -]); \ No newline at end of file +]); diff --git a/src/prebid.js b/src/prebid.js index ea9731ee568..3c2eecf8b66 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -102,11 +102,15 @@ function validateSizes(sizes, targLength) { return cleanSizes; } -// setting mediaTypes to ortb2 corresponding fields and opposite +// synchronize fields between mediaTypes[mediaType] and ortb2Imp[mediaType] export function syncOrtb2(adUnit, mediaType) { + const ortb2Imp = deepAccess(adUnit, `ortb2Imp.${mediaType}`); + const mediaTypes = deepAccess(adUnit, `mediaTypes.${mediaType}`); - const ortb2Imp = deepAccess(adUnit, `ortb2Imp.${mediaType}`) || {}; - const mediaTypes = deepAccess(adUnit, `mediaTypes.${mediaType}`) || {}; + if (!ortb2Imp && !mediaTypes) { + // omitting sync due to not present mediaType + return; + } const fields = { 'video': ORTB_VIDEO_PARAMS, @@ -119,20 +123,20 @@ export function syncOrtb2(adUnit, mediaType) { } fields.entries().forEach(([key, validator]) => { - const mediaTypesFieldValue = mediaTypes[key]; - const ortbFieldValue = ortb2Imp[key]; + const mediaTypesFieldValue = deepAccess(adUnit, `mediaTypes.${mediaType}.${key}`) // mediaTypes[key]; + const ortbFieldValue = deepAccess(adUnit, `ortb2Imp.${mediaType}.${key}`) // ortb2Imp[key]; if (mediaTypesFieldValue == undefined && ortbFieldValue == undefined) { - return; + } else if (mediaTypesFieldValue == undefined) { - mediaTypes[key] = ortbFieldValue; + deepSetValue(adUnit, `mediaTypes.${mediaType}.${key}`, ortbFieldValue); } else if (ortbFieldValue == undefined) { - ortb2Imp[key] = validator(mediaTypesFieldValue) ? mediaTypesFieldValue : undefined; + deepSetValue(adUnit, `ortb2Imp.${mediaType}.${key}`, mediaTypesFieldValue); } else { logWarn(`adUnit ${adUnit.code}: specifies conflicting ortb2Imp.${mediaType}.${key} and mediaTypes.${mediaType}.${key}, the latter will be ignored`, adUnit); - mediaTypes[key] = ortbFieldValue; + deepSetValue(adUnit, `mediaTypes.${mediaType}.${key}`, ortbFieldValue); } - }) + }); } function validateBannerMediaType(adUnit) { diff --git a/test/spec/banner_spec.js b/test/spec/banner_spec.js new file mode 100644 index 00000000000..9de9fdd2626 --- /dev/null +++ b/test/spec/banner_spec.js @@ -0,0 +1,183 @@ +import * as utils from '../../src/utils.js'; +import { syncOrtb2 } from '../../src/prebid.js'; + +describe('banner', () => { + describe('syncOrtb2', () => { + let logWarnSpy; + + beforeEach(function () { + logWarnSpy = sinon.spy(utils, 'logWarn'); + }); + + afterEach(function () { + utils.logWarn.restore(); + }); + + it('should properly sync fields if both present', () => { + const adUnit = { + mediaTypes: { + banner: { + format: [{w: 100, h: 100}], + btype: [1, 2, 34], + battr: [3, 4], + maxduration: 'omitted_value' // should be omitted during copying - not part of banner obj spec + } + }, + ortb2Imp: { + banner: { + request: '{payload: true}', + pos: 5, + btype: [999, 999], + vcm: 0, + foobar: 'omitted_value' // should be omitted during copying - not part of banner obj spec + } + } + }; + + const expected = { + mediaTypes: { + banner: { + format: [{w: 100, h: 100}], + btype: [999, 999], + pos: 5, + battr: [3, 4], + vcm: 0, + maxduration: 'omitted_value' + } + }, + ortb2Imp: { + banner: { + format: [{w: 100, h: 100}], + request: '{payload: true}', + pos: 5, + btype: [999, 999], + battr: [3, 4], + vcm: 0, + foobar: 'omitted_value' + } + } + }; + + syncOrtb2(adUnit, 'banner'); + expect(adUnit).to.deep.eql(expected); + + assert.ok(logWarnSpy.calledOnce, 'expected warning was logged due to conflicting btype'); + }); + + it('should omit sync if mediaType not present on adUnit', () => { + const adUnit = { + mediaTypes: { + video: { + fieldToOmit: 'omitted_value' + } + }, + ortb2Imp: { + video: { + fieldToOmit2: 'omitted_value' + } + } + }; + + syncOrtb2(adUnit, 'banner'); + + expect(adUnit.ortb2Imp.banner).to.be.undefined; + expect(adUnit.mediaTypes.banner).to.be.undefined; + }); + + it('should properly sync if mediaTypes is not present on any of side', () => { + const adUnit = { + mediaTypes: { + banner: { + format: [{w: 100, h: 100}], + btype: [999, 999], + pos: 5, + battr: [3, 4], + vcm: 0, + maxduration: 'omitted_value' + } + }, + ortb2Imp: {} + }; + + const expected1 = { + mediaTypes: { + banner: { + format: [{w: 100, h: 100}], + btype: [999, 999], + pos: 5, + battr: [3, 4], + vcm: 0, + maxduration: 'omitted_value' + } + }, + ortb2Imp: { + banner: { + format: [{w: 100, h: 100}], + btype: [999, 999], + pos: 5, + battr: [3, 4], + vcm: 0, + } + } + }; + + syncOrtb2(adUnit, 'banner'); + expect(adUnit).to.deep.eql(expected1); + + const adUnit2 = { + mediaTypes: {}, + ortb2Imp: { + banner: { + format: [{w: 100, h: 100}], + btype: [999, 999], + pos: 5, + battr: [3, 4], + vcm: 0, + maxduration: 'omitted_value' + } + } + }; + + const expected2 = { + mediaTypes: { + banner: { + format: [{w: 100, h: 100}], + btype: [999, 999], + pos: 5, + battr: [3, 4], + vcm: 0, + } + }, + ortb2Imp: { + banner: { + format: [{w: 100, h: 100}], + btype: [999, 999], + pos: 5, + battr: [3, 4], + vcm: 0, + maxduration: 'omitted_value' + } + } + }; + + syncOrtb2(adUnit2, 'banner'); + expect(adUnit2).to.deep.eql(expected2); + }); + + it('should not create empty banner object on ortb2Imp if there was nothing to copy', () => { + const adUnit2 = { + mediaTypes: { + banner: { + noOrtbBannerField1: 'value', + noOrtbBannerField2: 'value' + } + }, + ortb2Imp: { + // lack of banner field + } + }; + syncOrtb2(adUnit2, 'banner'); + expect(adUnit2.ortb2Imp.banner).to.be.undefined + }); + }); +}) diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 3736b5768bd..b88a0392dda 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -1437,45 +1437,159 @@ describe('toOrtbNativeResponse', () => { }) }) - it('should sync mediaTypes and ortb2Imp properly', () => { - const adUnit = { - mediaTypes: { - native: { - api: [6], - battr: [3, 4], - fieldToOmit: 'omitted_value' + describe('syncOrtb2', () => { + let logWarnSpy; + + beforeEach(function () { + logWarnSpy = sinon.spy(utils, 'logWarn'); + }); + + afterEach(function () { + utils.logWarn.restore(); + }); + + it('should properly sync fields if both present', () => { + const adUnit = { + mediaTypes: { + native: { + api: [6], + battr: [3, 4], + maxduration: 'omitted_value' // should be omitted during copying - not part of native obj spec + } + }, + ortb2Imp: { + native: { + request: '{payload: true}', + ver: '1.1', + battr: [1, 2] + } } - }, - ortb2Imp: { - native: { - request: '{payload: true}', - ver: '1.1', - battr: [1, 2] - } - } - }; + }; - const expected = { - mediaTypes: { - native: { - api: [6], - request: '{payload: true}', - ver: '1.1', - fieldToOmit: 'omitted_value', - battr: [1, 2], + const expected = { + mediaTypes: { + native: { + api: [6], + request: '{payload: true}', + ver: '1.1', + maxduration: 'omitted_value', + battr: [1, 2], + } + }, + ortb2Imp: { + native: { + api: [6], + request: '{payload: true}', + ver: '1.1', + battr: [1, 2] + } } - }, - ortb2Imp: { - native: { - api: [6], - request: '{payload: true}', - ver: '1.1', - battr: [1, 2] - } - } - }; + }; - syncOrtb2(adUnit, 'native'); - expect(adUnit).to.deep.eql(expected); - }) + syncOrtb2(adUnit, 'native'); + expect(adUnit).to.deep.eql(expected); + + assert.ok(logWarnSpy.calledOnce, 'expected warning was logged due to conflicting battr'); + }); + + it('should omit sync if mediaType not present on adUnit', () => { + const adUnit = { + mediaTypes: { + audio: { + fieldToOmit: 'omitted_value' + } + }, + ortb2Imp: { + audio: { + fieldToOmit2: 'omitted_value' + } + } + }; + + syncOrtb2(adUnit, 'native'); + + expect(adUnit.ortb2Imp.native).to.be.undefined; + expect(adUnit.mediaTypes.native).to.be.undefined; + }); + + it('should properly sync if mediaTypes is not present on any of side', () => { + const adUnit = { + mediaTypes: { + native: { + api: [6], + request: '{payload: true}', + ver: '1.1', + } + }, + ortb2Imp: {} + }; + + const expected1 = { + mediaTypes: { + native: { + api: [6], + request: '{payload: true}', + ver: '1.1', + } + }, + ortb2Imp: { + native: { + api: [6], + request: '{payload: true}', + ver: '1.1', + } + } + }; + + syncOrtb2(adUnit, 'native'); + expect(adUnit).to.deep.eql(expected1); + + const adUnit2 = { + mediaTypes: {}, + ortb2Imp: { + native: { + api: [6], + request: '{payload: true}', + ver: '1.1', + } + } + }; + + const expected2 = { + mediaTypes: { + native: { + api: [6], + request: '{payload: true}', + ver: '1.1', + } + }, + ortb2Imp: { + native: { + api: [6], + request: '{payload: true}', + ver: '1.1' + } + } + }; + + syncOrtb2(adUnit2, 'native'); + expect(adUnit2).to.deep.eql(expected2); + }); + + it('should not create empty native object on ortb2Imp if there was nothing to copy', () => { + const adUnit2 = { + mediaTypes: { + native: { + noOrtbBannerField1: 'value', + noOrtbBannerField2: 'value' + } + }, + ortb2Imp: { + // lack of native field + } + }; + syncOrtb2(adUnit2, 'native'); + expect(adUnit2.ortb2Imp.native).to.be.undefined + }); + }); }) diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 60363ad359d..eeab94e768e 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -3746,71 +3746,4 @@ 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: {} - } - } - setBattrForAdUnit(adUnit, 'native'); - expect(adUnit.mediaTypes.native).to.deep.equal({}); - expect(adUnit.mediaTypes.ortb2Imp).to.not.exist; - }) - }) }); diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 18955771049..08ceee38e1a 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -2,6 +2,7 @@ import {fillVideoDefaults, isValidVideoBid, validateOrtbVideoFields} from 'src/v import {hook} from '../../src/hook.js'; import {stubAuctionIndex} from '../helpers/indexStub.js'; import * as utils from '../../src/utils.js'; +import { syncOrtb2 } from '../../src/prebid.js'; describe('video.js', function () { let sandbox; @@ -274,4 +275,199 @@ describe('video.js', function () { expect(valid).to.equal(false); }); }) + + describe('syncOrtb2', () => { + let logWarnSpy; + + beforeEach(function () { + logWarnSpy = sinon.spy(utils, 'logWarn'); + }); + + afterEach(function () { + utils.logWarn.restore(); + }); + + it('should properly sync fields if both present', () => { + const adUnit = { + mediaTypes: { + video: { + minduration: 500, + maxduration: 1000, + protocols: [1, 2, 3], + linearity: 5, + w: 100, + h: 200, + foo: 'omitted_value' // should be omitted during copying - not part of video obj spec + } + }, + ortb2Imp: { + video: { + minbitrate: 10, + maxbitrate: 50, + delivery: [1, 2, 3, 4], + linearity: 10, + bar: 'omitted_value' // should be omitted during copying - not part of video obj spec + } + } + }; + + const expected = { + mediaTypes: { + video: { + minduration: 500, + maxduration: 1000, + protocols: [1, 2, 3], + w: 100, + h: 200, + minbitrate: 10, + maxbitrate: 50, + delivery: [1, 2, 3, 4], + linearity: 10, + foo: 'omitted_value' + } + }, + ortb2Imp: { + video: { + minduration: 500, + maxduration: 1000, + protocols: [1, 2, 3], + w: 100, + h: 200, + minbitrate: 10, + maxbitrate: 50, + delivery: [1, 2, 3, 4], + linearity: 10, + bar: 'omitted_value' + } + } + }; + + syncOrtb2(adUnit, 'video'); + expect(adUnit).to.deep.eql(expected); + + assert.ok(logWarnSpy.calledOnce, 'expected warning was logged due to conflicting linearity'); + }); + + it('should omit sync if video mediaType not present on adUnit', () => { + const adUnit = { + mediaTypes: { + native: { + fieldToOmit: 'omitted_value' + } + }, + ortb2Imp: { + native: { + fieldToOmit2: 'omitted_value' + } + } + }; + + syncOrtb2(adUnit, 'video'); + + expect(adUnit.mediaTypes.video).to.be.undefined; + expect(adUnit.ortb2Imp.video).to.be.undefined; + }); + + it('should properly sync if mediaTypes is not present on any of side', () => { + const adUnit = { + mediaTypes: { + video: { + minduration: 500, + maxduration: 1000, + protocols: [1, 2, 3], + linearity: 5, + w: 100, + h: 200, + foo: 'omitted_value' + } + }, + ortb2Imp: {} + }; + + const expected1 = { + mediaTypes: { + video: { + minduration: 500, + maxduration: 1000, + protocols: [1, 2, 3], + linearity: 5, + w: 100, + h: 200, + foo: 'omitted_value' + } + }, + ortb2Imp: { + video: { + minduration: 500, + maxduration: 1000, + protocols: [1, 2, 3], + linearity: 5, + w: 100, + h: 200, + } + } + }; + + syncOrtb2(adUnit, 'video'); + expect(adUnit).to.deep.eql(expected1); + + const adUnit2 = { + mediaTypes: {}, + ortb2Imp: { + video: { + minduration: 500, + maxduration: 1000, + protocols: [1, 2, 3], + linearity: 5, + w: 100, + h: 200, + foo: 'omitted_value' + } + } + }; + + const expected2 = { + mediaTypes: { + video: { + minduration: 500, + maxduration: 1000, + protocols: [1, 2, 3], + linearity: 5, + w: 100, + h: 200, + } + }, + ortb2Imp: { + video: { + minduration: 500, + maxduration: 1000, + protocols: [1, 2, 3], + linearity: 5, + w: 100, + h: 200, + foo: 'omitted_value' + } + } + }; + + syncOrtb2(adUnit2, 'video'); + expect(adUnit2).to.deep.eql(expected2); + }); + + it('should not create empty video object on ortb2Imp if there was nothing to copy', () => { + const adUnit2 = { + mediaTypes: { + video: { + noOrtbVideoField1: 'value', + noOrtbVideoField2: 'value' + } + }, + ortb2Imp: { + // lack of video field + } + }; + syncOrtb2(adUnit2, 'video'); + expect(adUnit2.ortb2Imp.video).to.be.undefined + }); + }); }); From fdb27804022c25a6dc1a50bcbb1702e16445e71d Mon Sep 17 00:00:00 2001 From: Marcin Komorski Date: Thu, 7 Nov 2024 11:31:42 +0100 Subject: [PATCH 04/10] improvements --- src/prebid.js | 6 +++--- test/spec/banner_spec.js | 2 +- test/spec/native_spec.js | 6 +++--- test/spec/video_spec.js | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index 3c2eecf8b66..1261fbcb7be 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -123,11 +123,11 @@ export function syncOrtb2(adUnit, mediaType) { } fields.entries().forEach(([key, validator]) => { - const mediaTypesFieldValue = deepAccess(adUnit, `mediaTypes.${mediaType}.${key}`) // mediaTypes[key]; - const ortbFieldValue = deepAccess(adUnit, `ortb2Imp.${mediaType}.${key}`) // ortb2Imp[key]; + const mediaTypesFieldValue = deepAccess(adUnit, `mediaTypes.${mediaType}.${key}`) + const ortbFieldValue = deepAccess(adUnit, `ortb2Imp.${mediaType}.${key}`) if (mediaTypesFieldValue == undefined && ortbFieldValue == undefined) { - + // omitting the params if it's not defined on either of sides } else if (mediaTypesFieldValue == undefined) { deepSetValue(adUnit, `mediaTypes.${mediaType}.${key}`, ortbFieldValue); } else if (ortbFieldValue == undefined) { diff --git a/test/spec/banner_spec.js b/test/spec/banner_spec.js index 9de9fdd2626..e589e988d63 100644 --- a/test/spec/banner_spec.js +++ b/test/spec/banner_spec.js @@ -18,7 +18,7 @@ describe('banner', () => { mediaTypes: { banner: { format: [{w: 100, h: 100}], - btype: [1, 2, 34], + btype: [1, 2, 34], // should be overwritten with value from ortb2Imp battr: [3, 4], maxduration: 'omitted_value' // should be omitted during copying - not part of banner obj spec } diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index b88a0392dda..62f2a3ed11e 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -1453,7 +1453,7 @@ describe('toOrtbNativeResponse', () => { mediaTypes: { native: { api: [6], - battr: [3, 4], + battr: [3, 4], // should be overwritten with value from ortb2Imp maxduration: 'omitted_value' // should be omitted during copying - not part of native obj spec } }, @@ -1580,8 +1580,8 @@ describe('toOrtbNativeResponse', () => { const adUnit2 = { mediaTypes: { native: { - noOrtbBannerField1: 'value', - noOrtbBannerField2: 'value' + noOrtbNativeField1: 'value', + noOrtbNativeField2: 'value' } }, ortb2Imp: { diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 08ceee38e1a..2fb51acbbf2 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -294,7 +294,7 @@ describe('video.js', function () { minduration: 500, maxduration: 1000, protocols: [1, 2, 3], - linearity: 5, + linearity: 5, // should be overwritten with value from ortb2Imp w: 100, h: 200, foo: 'omitted_value' // should be omitted during copying - not part of video obj spec From 175e94b6d90723be01dda2c5854680c19b4d4d11 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Thu, 7 Nov 2024 15:16:59 +0100 Subject: [PATCH 05/10] improvement --- test/spec/video_spec.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 2fb51acbbf2..7af1bc78055 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -381,7 +381,9 @@ describe('video.js', function () { foo: 'omitted_value' } }, - ortb2Imp: {} + ortb2Imp: { + // lack of video field + } }; const expected1 = { @@ -412,7 +414,9 @@ describe('video.js', function () { expect(adUnit).to.deep.eql(expected1); const adUnit2 = { - mediaTypes: {}, + mediaTypes: { + // lack of video field + }, ortb2Imp: { video: { minduration: 500, From b1b013ef889b5b433ccc998f1dba47ce691eec48 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Thu, 7 Nov 2024 16:10:46 +0100 Subject: [PATCH 06/10] lint fix --- src/prebid.js | 4 ++-- test/spec/video_spec.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index 1261fbcb7be..4f253b199bb 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -123,8 +123,8 @@ export function syncOrtb2(adUnit, mediaType) { } fields.entries().forEach(([key, validator]) => { - const mediaTypesFieldValue = deepAccess(adUnit, `mediaTypes.${mediaType}.${key}`) - const ortbFieldValue = deepAccess(adUnit, `ortb2Imp.${mediaType}.${key}`) + const mediaTypesFieldValue = deepAccess(adUnit, `mediaTypes.${mediaType}.${key}`); + const ortbFieldValue = deepAccess(adUnit, `ortb2Imp.${mediaType}.${key}`); if (mediaTypesFieldValue == undefined && ortbFieldValue == undefined) { // omitting the params if it's not defined on either of sides diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 7af1bc78055..b9c03eaa797 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -449,7 +449,7 @@ describe('video.js', function () { linearity: 5, w: 100, h: 200, - foo: 'omitted_value' + foo: 'omitted_value', } } }; From a10a0d817944932d39700d6cabcbfefcdf2e3bc2 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Fri, 8 Nov 2024 14:20:46 +0100 Subject: [PATCH 07/10] type check --- src/prebid.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/prebid.js b/src/prebid.js index 4f253b199bb..c23dac438b9 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -122,7 +122,9 @@ export function syncOrtb2(adUnit, mediaType) { return; } - fields.entries().forEach(([key, validator]) => { + const params = fields.entries?.() || []; + + params.forEach(([key, validator]) => { const mediaTypesFieldValue = deepAccess(adUnit, `mediaTypes.${mediaType}.${key}`); const ortbFieldValue = deepAccess(adUnit, `ortb2Imp.${mediaType}.${key}`); From 2ae9306cb19917f2804f53c35a22354a9cc3bef8 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Fri, 8 Nov 2024 14:54:31 +0100 Subject: [PATCH 08/10] instance of Map check --- src/prebid.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index c23dac438b9..1ed31dc75ef 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -118,13 +118,11 @@ export function syncOrtb2(adUnit, mediaType) { 'banner': ORTB_BANNER_PARAMS }[mediaType]; - if (!fields) { + if (!fields || !(fields instanceof Map)) { return; } - const params = fields.entries?.() || []; - - params.forEach(([key, validator]) => { + fields.entries().forEach(([key, validator]) => { const mediaTypesFieldValue = deepAccess(adUnit, `mediaTypes.${mediaType}.${key}`); const ortbFieldValue = deepAccess(adUnit, `ortb2Imp.${mediaType}.${key}`); From 39d5e188022b046e1aefe8309fb9e68b7f27aeb0 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Fri, 8 Nov 2024 15:44:28 +0100 Subject: [PATCH 09/10] get rid of entries --- src/prebid.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/prebid.js b/src/prebid.js index 1ed31dc75ef..e33ca11520a 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -118,11 +118,11 @@ export function syncOrtb2(adUnit, mediaType) { 'banner': ORTB_BANNER_PARAMS }[mediaType]; - if (!fields || !(fields instanceof Map)) { + if (!fields) { return; } - fields.entries().forEach(([key, validator]) => { + [...fields].forEach(([key, validator]) => { const mediaTypesFieldValue = deepAccess(adUnit, `mediaTypes.${mediaType}.${key}`); const ortbFieldValue = deepAccess(adUnit, `ortb2Imp.${mediaType}.${key}`); From d092151be69df499e2b97100d80d5a4ab3c152e0 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Wed, 20 Nov 2024 23:39:37 +0100 Subject: [PATCH 10/10] removing native from syncOrtb2 --- src/native.js | 14 ---- src/prebid.js | 8 +- test/spec/native_spec.js | 159 +-------------------------------------- 3 files changed, 4 insertions(+), 177 deletions(-) diff --git a/src/native.js b/src/native.js index 782d316b551..a641beb71d5 100644 --- a/src/native.js +++ b/src/native.js @@ -3,12 +3,10 @@ import { deepClone, getDefinedParams, insertHtmlIntoIframe, isArray, - isArrayOfNums, isBoolean, isInteger, isNumber, isPlainObject, - isStr, logError, pick, triggerPixel @@ -20,18 +18,6 @@ import {NATIVE} from './mediaTypes.js'; import {getRenderingData} from './adRendering.js'; import {getCreativeRendererSource} from './creativeRenderers.js'; -/** - * List of OpenRTB 2.x native object properties with simple validators. - * Not included: `ext` - * reference: https://github.com/InteractiveAdvertisingBureau/openrtb2.x/blob/main/2.6.md - */ -export const ORTB_NATIVE_PARAMS = new Map([ - [ 'request', isStr ], - [ 'ver', isStr ], - [ 'api', isArrayOfNums ], - [ 'battr', isArrayOfNums ] -]); - /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid diff --git a/src/prebid.js b/src/prebid.js index e33ca11520a..fbfc271a14c 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -42,8 +42,8 @@ import {allConsent} from './consentHandler.js'; import {insertLocatorFrame, markBidAsRendered, renderAdDirect, renderIfDeferred} from './adRendering.js'; import {getHighestCpm} from './utils/reducers.js'; import {ORTB_VIDEO_PARAMS, fillVideoDefaults, validateOrtbVideoFields} from './video.js'; -import { ORTB_NATIVE_PARAMS } from './native.js'; import { ORTB_BANNER_PARAMS } from './banner.js'; +import { BANNER, VIDEO } from './mediaTypes.js'; const pbjsInstance = getGlobal(); const { triggerUserSyncs } = userSync; @@ -113,9 +113,8 @@ export function syncOrtb2(adUnit, mediaType) { } const fields = { - 'video': ORTB_VIDEO_PARAMS, - 'native': ORTB_NATIVE_PARAMS, - 'banner': ORTB_BANNER_PARAMS + [VIDEO]: ORTB_VIDEO_PARAMS, + [BANNER]: ORTB_BANNER_PARAMS }[mediaType]; if (!fields) { @@ -225,7 +224,6 @@ 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; } - syncOrtb2(validatedAdUnit, 'native'); return validatedAdUnit; } diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 62f2a3ed11e..01214cdb3ae 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -22,8 +22,7 @@ import { convertOrtbRequestToProprietaryNative, fromOrtbNativeRequest } from '.. import {auctionManager} from '../../src/auctionManager.js'; import {getRenderingData} from '../../src/adRendering.js'; import {getCreativeRendererSource} from '../../src/creativeRenderers.js'; -import {deepClone, deepSetValue} from '../../src/utils.js'; -import { syncOrtb2 } from '../../src/prebid.js'; +import {deepSetValue} from '../../src/utils.js'; const utils = require('src/utils'); const bid = { @@ -1436,160 +1435,4 @@ describe('toOrtbNativeResponse', () => { } }) }) - - describe('syncOrtb2', () => { - let logWarnSpy; - - beforeEach(function () { - logWarnSpy = sinon.spy(utils, 'logWarn'); - }); - - afterEach(function () { - utils.logWarn.restore(); - }); - - it('should properly sync fields if both present', () => { - const adUnit = { - mediaTypes: { - native: { - api: [6], - battr: [3, 4], // should be overwritten with value from ortb2Imp - maxduration: 'omitted_value' // should be omitted during copying - not part of native obj spec - } - }, - ortb2Imp: { - native: { - request: '{payload: true}', - ver: '1.1', - battr: [1, 2] - } - } - }; - - const expected = { - mediaTypes: { - native: { - api: [6], - request: '{payload: true}', - ver: '1.1', - maxduration: 'omitted_value', - battr: [1, 2], - } - }, - ortb2Imp: { - native: { - api: [6], - request: '{payload: true}', - ver: '1.1', - battr: [1, 2] - } - } - }; - - syncOrtb2(adUnit, 'native'); - expect(adUnit).to.deep.eql(expected); - - assert.ok(logWarnSpy.calledOnce, 'expected warning was logged due to conflicting battr'); - }); - - it('should omit sync if mediaType not present on adUnit', () => { - const adUnit = { - mediaTypes: { - audio: { - fieldToOmit: 'omitted_value' - } - }, - ortb2Imp: { - audio: { - fieldToOmit2: 'omitted_value' - } - } - }; - - syncOrtb2(adUnit, 'native'); - - expect(adUnit.ortb2Imp.native).to.be.undefined; - expect(adUnit.mediaTypes.native).to.be.undefined; - }); - - it('should properly sync if mediaTypes is not present on any of side', () => { - const adUnit = { - mediaTypes: { - native: { - api: [6], - request: '{payload: true}', - ver: '1.1', - } - }, - ortb2Imp: {} - }; - - const expected1 = { - mediaTypes: { - native: { - api: [6], - request: '{payload: true}', - ver: '1.1', - } - }, - ortb2Imp: { - native: { - api: [6], - request: '{payload: true}', - ver: '1.1', - } - } - }; - - syncOrtb2(adUnit, 'native'); - expect(adUnit).to.deep.eql(expected1); - - const adUnit2 = { - mediaTypes: {}, - ortb2Imp: { - native: { - api: [6], - request: '{payload: true}', - ver: '1.1', - } - } - }; - - const expected2 = { - mediaTypes: { - native: { - api: [6], - request: '{payload: true}', - ver: '1.1', - } - }, - ortb2Imp: { - native: { - api: [6], - request: '{payload: true}', - ver: '1.1' - } - } - }; - - syncOrtb2(adUnit2, 'native'); - expect(adUnit2).to.deep.eql(expected2); - }); - - it('should not create empty native object on ortb2Imp if there was nothing to copy', () => { - const adUnit2 = { - mediaTypes: { - native: { - noOrtbNativeField1: 'value', - noOrtbNativeField2: 'value' - } - }, - ortb2Imp: { - // lack of native field - } - }; - syncOrtb2(adUnit2, 'native'); - expect(adUnit2.ortb2Imp.native).to.be.undefined - }); - }); })