From ccef1078e16a152f428a3e959b5b56b85f236a50 Mon Sep 17 00:00:00 2001 From: Michele Nasti Date: Tue, 26 Jul 2022 15:15:53 +0200 Subject: [PATCH] Prebid Core: switch native assets to ortb2 format (#8086) * switch native assets to ortb2 format * put rendererUrl, adTemplate back in message * rename ortb2 to ortb * typo fix * changes to support ortb for native media type * handle bid won case * mark bid as won on any post message for native * fix tests for bid_won event for native * add missing imports * native converting functions * convert new native ortb media type object to proprietary format for native bidders * fix LGTM.com issue * added comments; minor fixes * added test for convertOrtbRequestToProprietaryNative + fixes * add nativeParams to conversion * support nativeParams * check that when native.ortb is present, it's the only property * remove commented code * removed unnecessary tests * added test that checks that BID_WON is not fired more than once for the same adId * validation is now performed on ortb data only * fix for prebidServer_native_example.html * PrebidServerBidAdapter also responds in ortb format * fix aspect_ratios as an array in tests * LGTM fix - remove unused variables * Better name for native constants * use WeakSet instead of Set * when native request is openRTB, the whole native.ortb is passed over * retain some defaults dor native PBS request * fix for empty ortbRequest in nativeBidIsValid * add non-asset properties to media type object * final fixes after rebasing * handle tracking of soon-deprecated ortb imptrackers and jstracker * let native ad unit take all horizontal space * pass "mapping" from legacy native to ortb to prebid universal creative * Convert ortb assets to legacy for backward compatibility with legacy templates * add ortb conversion for more bid adapters * wrap conversion function in FEATURES.NATIVE * remove expired bids from native wrapper * instead of modifying bidresponse, use nativeReq * wrap test in FEATURES.NATIVE * fix for native mapping in prebidServerBidAdapter * use copy-on-write to convert ortb requests to legacy * fix comment on function * Update criteoBidAdapter.js Co-authored-by: Filip Stamenkovic Co-authored-by: Michele Nasti Co-authored-by: Patrick McCann --- .../gpt/prebidServer_native_example.html | 4 +- modules/ablidaBidAdapter.js | 4 + modules/adagioBidAdapter.js | 4 + modules/adbookpspBidAdapter.js | 4 + modules/adfBidAdapter.js | 4 + modules/adgenerationBidAdapter.js | 3 + modules/adkernelBidAdapter.js | 4 + modules/admanBidAdapter.js | 4 + modules/admixerBidAdapter.js | 4 + modules/adnowBidAdapter.js | 4 + modules/adotBidAdapter.js | 3 + modules/adprimeBidAdapter.js | 4 + modules/adrelevantisBidAdapter.js | 4 + modules/adrinoBidAdapter.js | 3 + modules/adtrueBidAdapter.js | 4 + modules/aduptechBidAdapter.js | 4 + modules/adxcgBidAdapter.js | 4 + modules/adyoulikeBidAdapter.js | 3 + modules/ajaBidAdapter.js | 4 + modules/appnexusBidAdapter.js | 4 + modules/apstreamBidAdapter.js | 3 + modules/bidscubeBidAdapter.js | 4 + modules/bizzclickBidAdapter.js | 4 + modules/boldwinBidAdapter.js | 4 + modules/braveBidAdapter.js | 4 + modules/bridgewellBidAdapter.js | 4 + modules/buzzoolaBidAdapter.js | 4 + modules/clickforceBidAdapter.js | 4 + modules/colossussspBidAdapter.js | 4 + modules/compassBidAdapter.js | 4 + modules/contentexchangeBidAdapter.js | 4 + modules/craftBidAdapter.js | 4 + modules/criteoBidAdapter.js | 4 + modules/dailyhuntBidAdapter.js | 4 + modules/datablocksBidAdapter.js | 4 + modules/dianomiBidAdapter.js | 3 + modules/e_volutionBidAdapter.js | 4 + modules/engageyaBidAdapter.js | 5 + modules/finativeBidAdapter.js | 3 + modules/goldbachBidAdapter.js | 4 + modules/gothamadsBidAdapter.js | 4 + modules/growadvertisingBidAdapter.js | 4 + modules/improvedigitalBidAdapter.js | 4 + modules/iqzoneBidAdapter.js | 4 + modules/ixBidAdapter.js | 3 + modules/krushmediaBidAdapter.js | 4 + modules/livewrappedBidAdapter.js | 4 + modules/loganBidAdapter.js | 4 + modules/logicadBidAdapter.js | 4 + modules/loglyliftBidAdapter.js | 4 + modules/lunamediahbBidAdapter.js | 4 + modules/mathildeadsBidAdapter.js | 4 + modules/mediaforceBidAdapter.js | 4 + modules/mediafuseBidAdapter.js | 3 + modules/mediakeysBidAdapter.js | 4 + modules/medianetBidAdapter.js | 4 + modules/mediasquareBidAdapter.js | 4 + modules/mgidBidAdapter.js | 4 + modules/microadBidAdapter.js | 4 + modules/mobfoxpbBidAdapter.js | 3 + modules/my6senseBidAdapter.js | 4 + modules/nextrollBidAdapter.js | 3 + modules/operaadsBidAdapter.js | 4 + modules/orbidderBidAdapter.js | 4 + modules/outbrainBidAdapter.js | 3 + modules/ozoneBidAdapter.js | 4 + modules/prebidServerBidAdapter/index.js | 129 ++-- modules/pubmaticBidAdapter.js | 3 + modules/pubwiseBidAdapter.js | 4 + modules/pulsepointBidAdapter.js | 4 + modules/readpeakBidAdapter.js | 4 + modules/revcontentBidAdapter.js | 4 + modules/rtbhouseBidAdapter.js | 4 + modules/seedingAllianceBidAdapter.js | 4 + modules/smarthubBidAdapter.js | 3 + modules/smartyadsBidAdapter.js | 4 + modules/sspBCBidAdapter.js | 4 + modules/talkadsBidAdapter.js | 3 + modules/temedyaBidAdapter.js | 4 + modules/theAdxBidAdapter.js | 4 + modules/trafficgateBidAdapter.js | 3 + modules/ucfunnelBidAdapter.js | 4 + modules/ventesBidAdapter.js | 4 + modules/vibrantmediaBidAdapter.js | 4 + modules/videoheroesBidAdapter.js | 3 + modules/yieldlabBidAdapter.js | 4 + src/constants.json | 52 +- src/native.js | 504 ++++++++++++++- src/prebid.js | 10 + src/secureCreatives.js | 17 +- .../modules/prebidServerBidAdapter_spec.js | 272 +++++--- test/spec/native_spec.js | 587 +++++++++++++++--- test/spec/unit/core/bidderFactory_spec.js | 10 +- test/spec/unit/pbjs_api_spec.js | 49 +- test/spec/unit/secureCreatives_spec.js | 94 +-- 95 files changed, 1652 insertions(+), 396 deletions(-) diff --git a/integrationExamples/gpt/prebidServer_native_example.html b/integrationExamples/gpt/prebidServer_native_example.html index 16c7d38a427..c590f0bcee5 100644 --- a/integrationExamples/gpt/prebidServer_native_example.html +++ b/integrationExamples/gpt/prebidServer_native_example.html @@ -133,8 +133,8 @@ ', + javascriptTrackers: '', ext: { foo: 'foo-value', - baz: 'baz-value' - } - } + baz: 'baz-value', + }, + }, }; const bidWithUndefinedFields = { @@ -50,12 +54,12 @@ const bidWithUndefinedFields = { clickUrl: 'https://www.link.example', clickTrackers: ['https://tracker.example'], impressionTrackers: ['https://impression.example'], - javascriptTrackers: '', + javascriptTrackers: '', ext: { foo: 'foo-value', - baz: undefined - } - } + baz: undefined, + }, + }, }; describe('native.js', function () { @@ -80,7 +84,9 @@ describe('native.js', function () { const targeting = getNativeTargeting(bid); expect(targeting[CONSTANTS.NATIVE_KEYS.title]).to.equal(bid.native.title); expect(targeting[CONSTANTS.NATIVE_KEYS.body]).to.equal(bid.native.body); - expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal(bid.native.clickUrl); + expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal( + bid.native.clickUrl + ); expect(targeting.hb_native_foo).to.equal(bid.native.foo); }); @@ -92,19 +98,23 @@ describe('native.js', function () { clickUrl: { sendId: true }, ext: { foo: { - sendId: false + sendId: false, }, baz: { - sendId: true - } - } - } + sendId: true, + }, + }, + }, }; const targeting = getNativeTargeting(bid, deps(adUnit)); expect(targeting[CONSTANTS.NATIVE_KEYS.title]).to.equal(bid.native.title); - expect(targeting[CONSTANTS.NATIVE_KEYS.body]).to.equal('hb_native_body:123'); - expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal('hb_native_linkurl:123'); + expect(targeting[CONSTANTS.NATIVE_KEYS.body]).to.equal( + 'hb_native_body:123' + ); + expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal( + 'hb_native_linkurl:123' + ); expect(targeting.hb_native_foo).to.equal(bid.native.ext.foo); expect(targeting.hb_native_baz).to.equal('hb_native_baz:123'); }); @@ -117,13 +127,13 @@ describe('native.js', function () { clickUrl: { sendId: true }, ext: { foo: { - required: false + required: false, }, baz: { - required: false - } - } - } + required: false, + }, + }, + }, }; const targeting = getNativeTargeting(bidWithUndefinedFields, deps(adUnit)); @@ -132,7 +142,7 @@ describe('native.js', function () { CONSTANTS.NATIVE_KEYS.title, CONSTANTS.NATIVE_KEYS.sponsoredBy, CONSTANTS.NATIVE_KEYS.clickUrl, - 'hb_native_foo' + 'hb_native_foo', ]); }); @@ -142,22 +152,19 @@ describe('native.js', function () { nativeParams: { image: { required: true, - sizes: [150, 50] + sizes: [150, 50], }, title: { required: true, len: 80, - sendTargetingKeys: true + sendTargetingKeys: true, }, sendTargetingKeys: false, - } - + }, }; const targeting = getNativeTargeting(bid, deps(adUnit)); - expect(Object.keys(targeting)).to.deep.equal([ - CONSTANTS.NATIVE_KEYS.title - ]); + expect(Object.keys(targeting)).to.deep.equal([CONSTANTS.NATIVE_KEYS.title]); }); it('should only include targeting if sendTargetingKeys not set to false', function () { @@ -166,38 +173,37 @@ describe('native.js', function () { nativeParams: { image: { required: true, - sizes: [150, 50] + sizes: [150, 50], }, title: { required: true, - len: 80 + len: 80, }, body: { - required: true + required: true, }, clickUrl: { - required: true + required: true, }, icon: { required: false, - sendTargetingKeys: false + sendTargetingKeys: false, }, cta: { required: false, - sendTargetingKeys: false + sendTargetingKeys: false, }, sponsoredBy: { required: false, - sendTargetingKeys: false + sendTargetingKeys: false, }, ext: { foo: { required: false, - sendTargetingKeys: true - } - } - } - + sendTargetingKeys: true, + }, + }, + }, }; const targeting = getNativeTargeting(bid, deps(adUnit)); @@ -206,7 +212,7 @@ describe('native.js', function () { CONSTANTS.NATIVE_KEYS.body, CONSTANTS.NATIVE_KEYS.image, CONSTANTS.NATIVE_KEYS.clickUrl, - 'hb_native_foo' + 'hb_native_foo', ]); }); @@ -216,17 +222,16 @@ describe('native.js', function () { nativeParams: { image: { required: true, - sizes: [150, 50] + sizes: [150, 50], }, title: { required: true, len: 80, }, rendererUrl: { - url: 'https://www.renderer.com/' - } - } - + url: 'https://www.renderer.com/', + }, + }, }; const targeting = getNativeTargeting(bid, deps(adUnit)); @@ -238,7 +243,7 @@ describe('native.js', function () { CONSTANTS.NATIVE_KEYS.icon, CONSTANTS.NATIVE_KEYS.sponsoredBy, CONSTANTS.NATIVE_KEYS.clickUrl, - CONSTANTS.NATIVE_KEYS.rendererUrl + CONSTANTS.NATIVE_KEYS.rendererUrl, ]); expect(bid.native.rendererUrl).to.deep.equal('https://www.renderer.com/'); @@ -251,15 +256,14 @@ describe('native.js', function () { nativeParams: { image: { required: true, - sizes: [150, 50] + sizes: [150, 50], }, title: { required: true, len: 80, }, - adTemplate: '

##hb_native_body##<\/p><\/div>' - } - + adTemplate: '

##hb_native_body##

', + }, }; const targeting = getNativeTargeting(bid, deps(adUnit)); @@ -270,10 +274,12 @@ describe('native.js', function () { CONSTANTS.NATIVE_KEYS.image, CONSTANTS.NATIVE_KEYS.icon, CONSTANTS.NATIVE_KEYS.sponsoredBy, - CONSTANTS.NATIVE_KEYS.clickUrl + CONSTANTS.NATIVE_KEYS.clickUrl, ]); - expect(bid.native.adTemplate).to.deep.equal('

##hb_native_body##<\/p><\/div>'); + expect(bid.native.adTemplate).to.deep.equal( + '

##hb_native_body##

' + ); delete bid.native.adTemplate; }); @@ -281,7 +287,10 @@ describe('native.js', function () { fireNativeTrackers({}, bid); sinon.assert.calledOnce(triggerPixelStub); sinon.assert.calledWith(triggerPixelStub, bid.native.impressionTrackers[0]); - sinon.assert.calledWith(insertHtmlIntoIframeStub, bid.native.javascriptTrackers); + sinon.assert.calledWith( + insertHtmlIntoIframeStub, + bid.native.javascriptTrackers + ); }); it('fires click trackers', function () { @@ -291,7 +300,7 @@ describe('native.js', function () { sinon.assert.calledWith(triggerPixelStub, bid.native.clickTrackers[0]); }); - it('creates native asset message', function() { + it('creates native asset message', function () { const messageRequest = { message: 'Prebid Native', action: 'assetRequest', @@ -304,19 +313,19 @@ describe('native.js', function () { expect(message.assets.length).to.equal(3); expect(message.assets).to.deep.include({ key: 'body', - value: bid.native.body + value: bid.native.body, }); expect(message.assets).to.deep.include({ key: 'image', - value: bid.native.image.url + value: bid.native.image.url, }); expect(message.assets).to.deep.include({ key: 'clickUrl', - value: bid.native.clickUrl + value: bid.native.clickUrl, }); }); - it('creates native all asset message', function() { + it('creates native all asset message', function () { const messageRequest = { message: 'Prebid Native', action: 'allAssetRequest', @@ -328,43 +337,43 @@ describe('native.js', function () { expect(message.assets.length).to.equal(9); expect(message.assets).to.deep.include({ key: 'body', - value: bid.native.body + value: bid.native.body, }); expect(message.assets).to.deep.include({ key: 'image', - value: bid.native.image.url + value: bid.native.image.url, }); expect(message.assets).to.deep.include({ key: 'clickUrl', - value: bid.native.clickUrl + value: bid.native.clickUrl, }); expect(message.assets).to.deep.include({ key: 'title', - value: bid.native.title + value: bid.native.title, }); expect(message.assets).to.deep.include({ key: 'icon', - value: bid.native.icon.url + value: bid.native.icon.url, }); expect(message.assets).to.deep.include({ key: 'cta', - value: bid.native.cta + value: bid.native.cta, }); expect(message.assets).to.deep.include({ key: 'sponsoredBy', - value: bid.native.sponsoredBy + value: bid.native.sponsoredBy, }); expect(message.assets).to.deep.include({ key: 'foo', - value: bid.native.ext.foo + value: bid.native.ext.foo, }); expect(message.assets).to.deep.include({ key: 'baz', - value: bid.native.ext.baz + value: bid.native.ext.baz, }); }); - it('creates native all asset message with only defined fields', function() { + it('creates native all asset message with only defined fields', function () { const messageRequest = { message: 'Prebid Native', action: 'allAssetRequest', @@ -376,23 +385,97 @@ describe('native.js', function () { expect(message.assets.length).to.equal(4); expect(message.assets).to.deep.include({ key: 'clickUrl', - value: bid.native.clickUrl + value: bid.native.clickUrl, }); expect(message.assets).to.deep.include({ key: 'title', - value: bid.native.title + value: bid.native.title, }); expect(message.assets).to.deep.include({ key: 'sponsoredBy', - value: bid.native.sponsoredBy + value: bid.native.sponsoredBy, }); expect(message.assets).to.deep.include({ key: 'foo', - value: bid.native.ext.foo + value: bid.native.ext.foo, }); }); }); +describe('validate native openRTB', function () { + it('should validate openRTB request', function () { + let openRTBNativeRequest = { assets: [] }; + // assets array can't be empty + expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(false); + openRTBNativeRequest.assets.push({ + id: 1.5, + required: 1, + title: {}, + }); + + // asset.id must be integer + expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(false); + openRTBNativeRequest.assets[0].id = 1; + // title must have 'len' property + expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(false); + openRTBNativeRequest.assets[0].title.len = 140; + // openRTB request is valid + expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(true); + + openRTBNativeRequest.assets.push({ + id: 2, + required: 1, + video: { + mimes: [], + protocols: [], + minduration: 50, + }, + }); + // video asset should have all required properties + expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(false); + openRTBNativeRequest.assets[1].video.maxduration = 60; + expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(true); + }); + + it('should validate openRTB native bid', function () { + const openRTBRequest = { + assets: [ + { + id: 1, + required: 1, + }, + { + id: 2, + required: 0, + }, + { + id: 3, + required: 1, + }, + ], + }; + let openRTBBid = { + assets: [ + { + id: 1, + }, + { + id: 2, + }, + ], + }; + + // link is missing + expect(isNativeOpenRTBBidValid(openRTBBid, openRTBRequest)).to.eq(false); + openRTBBid.link = { url: 'www.foo.bar' }; + // required id == 3 is missing + expect(isNativeOpenRTBBidValid(openRTBBid, openRTBRequest)).to.eq(false); + + openRTBBid.assets[1].id = 3; + expect(isNativeOpenRTBBidValid(openRTBBid, openRTBRequest)).to.eq(true); + }); +}); + describe('validate native', function () { const adUnit = { transactionId: 'test_adunit', @@ -407,15 +490,15 @@ describe('validate native', function () { image: { required: true, sizes: [150, 50], - aspect_ratios: [150, 50] + aspect_ratios: [150, 50], }, icon: { required: true, - sizes: [50, 50] + sizes: [50, 50], }, - } - } - } + }, + }, + }; let validBid = { adId: 'abc123', @@ -424,23 +507,24 @@ describe('validate native', function () { adUnitCode: '123/prebid_native_adunit', bidder: 'test_bidder', native: { - body: 'This is a Prebid Native Creative. There are many like it, but this one is mine.', + body: + 'This is a Prebid Native Creative. There are many like it, but this one is mine.', clickTrackers: ['http://my.click.tracker/url'], icon: { url: 'http://my.image.file/ad_image.jpg', height: 75, - width: 75 + width: 75, }, image: { url: 'http://my.icon.file/ad_icon.jpg', height: 2250, - width: 3000 + width: 3000, }, clickUrl: 'http://prebid.org/dev-docs/show-native-ads.html', impressionTrackers: ['http://my.imp.tracker/url'], - javascriptTrackers: '', - title: 'This is an example Prebid Native creative' - } + javascriptTrackers: '', + title: 'This is an example Prebid Native creative', + }, }; let noIconDimBid = { @@ -450,19 +534,20 @@ describe('validate native', function () { adUnitCode: '123/prebid_native_adunit', bidder: 'test_bidder', native: { - body: 'This is a Prebid Native Creative. There are many like it, but this one is mine.', + body: + 'This is a Prebid Native Creative. There are many like it, but this one is mine.', clickTrackers: ['http://my.click.tracker/url'], icon: 'http://my.image.file/ad_image.jpg', image: { url: 'http://my.icon.file/ad_icon.jpg', height: 2250, - width: 3000 + width: 3000, }, clickUrl: 'http://prebid.org/dev-docs/show-native-ads.html', impressionTrackers: ['http://my.imp.tracker/url'], - javascriptTrackers: '', - title: 'This is an example Prebid Native creative' - } + javascriptTrackers: '', + title: 'This is an example Prebid Native creative', + }, }; let noImgDimBid = { @@ -472,19 +557,20 @@ describe('validate native', function () { adUnitCode: '123/prebid_native_adunit', bidder: 'test_bidder', native: { - body: 'This is a Prebid Native Creative. There are many like it, but this one is mine.', + body: + 'This is a Prebid Native Creative. There are many like it, but this one is mine.', clickTrackers: ['http://my.click.tracker/url'], icon: { url: 'http://my.image.file/ad_image.jpg', height: 75, - width: 75 + width: 75, }, image: 'http://my.icon.file/ad_icon.jpg', clickUrl: 'http://prebid.org/dev-docs/show-native-ads.html', impressionTrackers: ['http://my.imp.tracker/url'], - javascriptTrackers: '', - title: 'This is an example Prebid Native creative' - } + javascriptTrackers: '', + title: 'This is an example Prebid Native creative', + }, }; beforeEach(function () {}); @@ -493,12 +579,307 @@ describe('validate native', function () { it('should accept bid if no image sizes are defined', function () { decorateAdUnitsWithNativeParams([adUnit]); - const index = stubAuctionIndex({adUnits: [adUnit]}) - let result = nativeBidIsValid(validBid, {index}); + const index = stubAuctionIndex({ adUnits: [adUnit] }); + let result = nativeBidIsValid(validBid, { index }); expect(result).to.be.true; - result = nativeBidIsValid(noIconDimBid, {index}); + result = nativeBidIsValid(noIconDimBid, { index }); expect(result).to.be.true; - result = nativeBidIsValid(noImgDimBid, {index}); + result = nativeBidIsValid(noImgDimBid, { index }); expect(result).to.be.true; }); + + it('should convert from old-style native to OpenRTB request', () => { + const adUnit = { + transactionId: 'test_adunit', + mediaTypes: { + native: { + title: { + required: true, + }, + body: { + required: true, + len: 45 + }, + image: { + required: true, + sizes: [150, 50], + aspect_ratios: [{ + min_width: 150, + min_height: 50 + }] + }, + icon: { + required: true, + aspect_ratios: [{ + min_width: 150, + min_height: 50 + }] + }, + address: {}, + }, + }, + }; + + const ortb = toOrtbNativeRequest(adUnit.mediaTypes.native); + expect(ortb).to.be.a('object'); + expect(ortb.assets).to.be.a('array'); + + // title + expect(ortb.assets[0]).to.deep.include({ + id: 0, + required: 1, + title: { + len: 140 + } + }); + + // body => data + expect(ortb.assets[1]).to.deep.include({ + id: 1, + required: 1, + data: { + type: 2, + len: 45 + } + }); + + // image => image + expect(ortb.assets[2]).to.deep.include({ + id: 2, + required: 1, + img: { + type: 3, // Main Image + w: 150, + h: 50, + } + }); + + expect(ortb.assets[3]).to.deep.include({ + id: 3, + required: 1, + img: { + type: 1, // Icon Image + wmin: 150, + hmin: 50, + } + }); + + expect(ortb.assets[4]).to.deep.include({ + id: 4, + required: 0, + data: { + type: 9, + } + }); + }); + + it('should convert from ortb to old-style native request', () => { + const openRTBRequest = { + 'ver': '1.2', + 'context': 2, + 'contextsubtype': 20, + 'plcmttype': 11, + 'plcmtcnt': 1, + 'aurlsupport': 0, + 'privacy': 1, + 'eventrackers': [ + { + 'event': 1, + 'methods': [1, 2] + }, + { + 'event': 2, + 'methods': [1] + } + ], + 'assets': [ + { + 'id': 123, + 'required': 1, + 'title': { + 'len': 140 + } + }, + { + 'id': 128, + 'required': 0, + 'img': { + 'wmin': 836, + 'hmin': 627, + 'type': 3 + } + }, + { + 'id': 124, + 'required': 1, + 'img': { + 'wmin': 50, + 'hmin': 50, + 'type': 1 + } + }, + { + 'id': 126, + 'required': 1, + 'data': { + 'type': 1, + 'len': 25 + } + }, + { + 'id': 127, + 'required': 1, + 'data': { + 'type': 2, + 'len': 140 + } + } + ] + }; + + const oldNativeRequest = fromOrtbNativeRequest(openRTBRequest); + + expect(oldNativeRequest).to.be.a('object'); + expect(oldNativeRequest.title).to.include({ + required: true, + len: 140 + }); + + expect(oldNativeRequest.image).to.deep.include({ + required: false, + aspect_ratios: { + min_width: 836, + min_height: 627, + ratio_width: 836, + ratio_height: 627 + } + }); + + expect(oldNativeRequest.icon).to.deep.include({ + required: true, + aspect_ratios: { + min_width: 50, + min_height: 50, + ratio_width: 50, + ratio_height: 50 + } + }); + expect(oldNativeRequest.sponsoredBy).to.include({ + required: true, + len: 25 + }) + expect(oldNativeRequest.body).to.include({ + required: true, + len: 140 + }) + }); + + if (FEATURES.NATIVE) { + it('should convert ortb bid requests to proprietary requests', () => { + const validBidRequests = [{ + bidId: 'bidId3', + adUnitCode: 'adUnitCode3', + transactionId: 'transactionId3', + mediaTypes: { + banner: {} + }, + params: { + publisher: 'publisher2', + placement: 'placement3' + } + }]; + const resultRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + expect(resultRequests).to.be.deep.equals(validBidRequests); + + validBidRequests[0].mediaTypes.native = { + ortb: { + ver: '1.2', + context: 2, + contextsubtype: 20, + plcmttype: 11, + plcmtcnt: 1, + aurlsupport: 0, + privacy: 1, + eventrackers: [ + { + event: 1, + methods: [1, 2] + }, + { + event: 2, + methods: [1] + } + ], + assets: [ + { + id: 123, + required: 1, + title: { + len: 140 + } + }, + { + id: 128, + required: 0, + img: { + wmin: 836, + hmin: 627, + type: 3 + } + }, + { + id: 124, + required: 1, + img: { + wmin: 50, + hmin: 50, + type: 1 + } + }, + { + id: 126, + required: 1, + data: { + type: 1, + len: 25 + } + }, + { + id: 127, + required: 1, + data: { + type: 2, + len: 140 + } + } + ] + } + }; + + const resultRequests2 = convertOrtbRequestToProprietaryNative(validBidRequests); + expect(resultRequests2[0].mediaTypes.native).to.deep.include({ + title: { + required: true, + len: 140 + }, + icon: { + required: true, + aspect_ratios: { + min_width: 50, + min_height: 50, + ratio_width: 50, + ratio_height: 50 + } + }, + sponsoredBy: { + required: true, + len: 25 + }, + body: { + required: true, + len: 140 + } + }); + }); + } }); diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index be68fc03765..646791c7e1f 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -39,6 +39,10 @@ function onTimelyResponseStub() { } +before(() => { + hook.ready(); +}); + let wrappedCallback = config.callbackWithBidder(CODE); describe('bidders created by newBidder', function () { @@ -47,10 +51,6 @@ describe('bidders created by newBidder', function () { let addBidResponseStub; let doneStub; - before(() => { - hook.ready(); - }); - beforeEach(function () { spec = { code: CODE, @@ -952,7 +952,7 @@ describe('validate bid response: ', function () { bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(addBidResponseStub.calledOnce).to.equal(false); - expect(logErrorSpy.callCount).to.equal(1); + expect(logErrorSpy.calledWithMatch('Ignoring bid: Native bid missing some required properties.')).to.equal(true); }); } diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 5c0391f96bf..3cee2b6b679 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -2376,14 +2376,47 @@ describe('Unit: Prebid Module', function () { $$PREBID_GLOBAL$$.requestBids({adUnits}); const spyArgs = adapterManager.callBids.getCall(0); const nativeRequest = spyArgs.args[1][0].bids[0].nativeParams; - expect(nativeRequest).to.deep.equal({ - image: {required: true}, - title: {required: true}, - sponsoredBy: {required: true}, - clickUrl: {required: true}, - body: {required: false}, - icon: {required: false}, - }); + expect(nativeRequest.ortb.assets).to.deep.equal([ + { + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }, + { + required: 1, + id: 2, + title: { + len: 140, + } + }, + { + required: 1, + id: 3, + data: { + type: 1, + } + }, + { + required: 0, + id: 4, + data: { + type: 2, + } + }, + { + required: 0, + id: 5, + img: { + type: 1, + wmin: 20, + hmin: 20, + } + }, + ]); resetAuction(); }); }); diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index 39fa9b9250f..7d5f9af35dd 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -1,7 +1,6 @@ import { _sendAdToCreative, getReplier, receiveMessage } from 'src/secureCreatives.js'; -import * as secureCreatives from 'src/secureCreatives.js'; import * as utils from 'src/utils.js'; import {getAdUnits, getBidRequests, getBidResponses} from 'test/fixtures/fixtures.js'; import {auctionManager} from 'src/auctionManager.js'; @@ -164,6 +163,7 @@ describe('secureCreatives', () => { stubGetAllAssetsMessage.restore(); stubEmit.restore(); resetAuction(); + adResponse.adId = bidId; }); describe('Prebid Request', function() { @@ -336,60 +336,17 @@ describe('secureCreatives', () => { sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); sinon.assert.calledOnce(ev.source.postMessage); sinon.assert.notCalled(stubFireNativeTrackers); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); - sinon.assert.notCalled(spyAddWinningBid); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); - }); - - it('Prebid native should allow stale rendering without config', function () { - pushBidResponseToAuction({}); - - const data = { - adId: bidId, - message: 'Prebid Native', - action: 'allAssetRequest' - }; - - const ev = makeEvent({ - data: JSON.stringify(data), - source: { - postMessage: sinon.stub() - }, - origin: 'any origin' - }); - - receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(stubGetAllAssetsMessage); - sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); - sinon.assert.calledOnce(ev.source.postMessage); - sinon.assert.notCalled(stubFireNativeTrackers); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); - sinon.assert.notCalled(spyAddWinningBid); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); - - resetHistories(ev.source.postMessage); - - receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(stubGetAllAssetsMessage); - sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); - sinon.assert.calledOnce(ev.source.postMessage); - sinon.assert.notCalled(stubFireNativeTrackers); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); - sinon.assert.notCalled(spyAddWinningBid); + sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); + sinon.assert.calledOnce(spyAddWinningBid); sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); }); - it('Prebid native should allow stale rendering with config', function () { - configObj.setConfig({'auctionOptions': {'suppressStaleRender': true}}); - - pushBidResponseToAuction({}); + it('Prebid native should not fire BID_WON when receiveMessage is called more than once', () => { + let adId = 3; + pushBidResponseToAuction({ adId }); const data = { - adId: bidId, + adId: adId, message: 'Prebid Native', action: 'allAssetRequest' }; @@ -403,37 +360,18 @@ describe('secureCreatives', () => { }); receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(stubGetAllAssetsMessage); - sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); - sinon.assert.calledOnce(ev.source.postMessage); - sinon.assert.notCalled(stubFireNativeTrackers); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); - sinon.assert.notCalled(spyAddWinningBid); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); - - resetHistories(ev.source.postMessage); + sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(stubGetAllAssetsMessage); - sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); - sinon.assert.calledOnce(ev.source.postMessage); - sinon.assert.notCalled(stubFireNativeTrackers); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); - sinon.assert.notCalled(spyAddWinningBid); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); - - configObj.setConfig({'auctionOptions': {}}); + stubEmit.withArgs(CONSTANTS.EVENTS.BID_WON, adResponse).calledOnce; }); it('Prebid native should fire trackers', function () { - pushBidResponseToAuction({}); + let adId = 2; + pushBidResponseToAuction({adId}); const data = { - adId: bidId, + adId: adId, message: 'Prebid Native', action: 'click', }; @@ -450,8 +388,8 @@ describe('secureCreatives', () => { sinon.assert.neverCalledWith(spyLogWarn, warning); sinon.assert.calledOnce(stubFireNativeTrackers); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); - sinon.assert.notCalled(spyAddWinningBid); + sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); + sinon.assert.calledOnce(spyAddWinningBid); resetHistories(ev.source.postMessage); @@ -461,8 +399,8 @@ describe('secureCreatives', () => { sinon.assert.neverCalledWith(spyLogWarn, warning); sinon.assert.calledOnce(stubFireNativeTrackers); - sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); - sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); + sinon.assert.notCalled(spyAddWinningBid); expect(adResponse).to.have.property('status', CONSTANTS.BID_STATUS.RENDERED); });