From c1b2842b151232fc7ffbcbbf118a6ef817f6dbe7 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Sat, 18 May 2024 05:37:55 -0700 Subject: [PATCH] Core: include dynamic renderer in native messages (#11343) --- src/native.js | 19 +- test/spec/native_spec.js | 394 ++++++++++++++++++++++----------------- 2 files changed, 244 insertions(+), 169 deletions(-) diff --git a/src/native.js b/src/native.js index f001200000d9..05347e47f1f7 100644 --- a/src/native.js +++ b/src/native.js @@ -15,6 +15,8 @@ import {includes} from './polyfill.js'; import {auctionManager} from './auctionManager.js'; import {NATIVE_ASSET_TYPES, NATIVE_IMAGE_TYPES, PREBID_NATIVE_DATA_KEYS_TO_ORTB, NATIVE_KEYS_THAT_ARE_NOT_ASSETS, NATIVE_KEYS} from './constants.js'; import {NATIVE} from './mediaTypes.js'; +import {getRenderingData} from './adRendering.js'; +import {getCreativeRendererSource} from './creativeRenderers.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -434,11 +436,24 @@ export function getNativeRenderingData(bid, adUnit, keys) { } function assetsMessage(data, adObject, keys, {index = auctionManager.index} = {}) { - return { + const msg = { message: 'assetResponse', adId: data.adId, - ...getNativeRenderingData(adObject, index.getAdUnit(adObject), keys) }; + let renderData = getRenderingData(adObject).native; + if (renderData) { + // if we have native rendering data (set up by the nativeRendering module) + // include it in full ("all assets") together with the renderer. + // this is to allow PUC to use dynamic renderers without requiring changes in creative setup + msg.native = Object.assign({}, renderData); + msg.renderer = getCreativeRendererSource(adObject); + if (keys != null) { + renderData.assets = renderData.assets.filter(({key}) => keys.includes(key)) + } + } else { + renderData = getNativeRenderingData(adObject, index.getAdUnit(adObject), keys); + } + return Object.assign(msg, renderData); } const NATIVE_KEYS_INVERTED = Object.fromEntries(Object.entries(NATIVE_KEYS).map(([k, v]) => [v, k])); diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 5d1a43cc57fc..a611a9dd789e 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -14,12 +14,15 @@ import { legacyPropertiesToOrtbNative, fireImpressionTrackers, fireClickTrackers, - setNativeResponseProperties, + setNativeResponseProperties, getNativeRenderingData, } from 'src/native.js'; import { NATIVE_KEYS } from 'src/constants.js'; import { stubAuctionIndex } from '../helpers/indexStub.js'; import { convertOrtbRequestToProprietaryNative, fromOrtbNativeRequest } from '../../src/native.js'; import {auctionManager} from '../../src/auctionManager.js'; +import {getRenderingData} from '../../src/adRendering.js'; +import {getCreativeRendererSource} from '../../src/creativeRenderers.js'; +import {deepClone} from '../../src/utils.js'; const utils = require('src/utils'); const bid = { @@ -180,6 +183,7 @@ const bidWithUndefinedFields = { }; describe('native.js', function () { + let sandbox; let triggerPixelStub; let insertHtmlIntoIframeStub; @@ -188,13 +192,13 @@ describe('native.js', function () { } beforeEach(function () { - triggerPixelStub = sinon.stub(utils, 'triggerPixel'); - insertHtmlIntoIframeStub = sinon.stub(utils, 'insertHtmlIntoIframe'); + sandbox = sinon.createSandbox(); + triggerPixelStub = sandbox.stub(utils, 'triggerPixel'); + insertHtmlIntoIframeStub = sandbox.stub(utils, 'insertHtmlIntoIframe'); }); afterEach(function () { - utils.triggerPixel.restore(); - utils.insertHtmlIntoIframe.restore(); + sandbox.restore(); }); it('gets native targeting keys', function () { @@ -382,171 +386,227 @@ describe('native.js', function () { })) }); - it('creates native asset message', function () { - const messageRequest = { - message: 'Prebid Native', - action: 'assetRequest', - adId: '123', - assets: ['hb_native_body', 'hb_native_image', 'hb_native_linkurl'], - }; - - const message = getAssetMessage(messageRequest, bid); - - expect(message.assets.length).to.equal(3); - expect(message.assets).to.deep.include({ - key: 'body', - value: bid.native.body, - }); - expect(message.assets).to.deep.include({ - key: 'image', - value: bid.native.image.url, - }); - expect(message.assets).to.deep.include({ - key: 'clickUrl', - value: bid.native.clickUrl, - }); - }); - - it('creates native all asset message', function () { - const messageRequest = { - message: 'Prebid Native', - action: 'allAssetRequest', - adId: '123', - }; - - const message = getAllAssetsMessage(messageRequest, bid); - - expect(message.assets.length).to.equal(10); - expect(message.assets).to.deep.include({ - key: 'body', - value: bid.native.body, - }); - expect(message.assets).to.deep.include({ - key: 'image', - value: bid.native.image.url, - }); - expect(message.assets).to.deep.include({ - key: 'clickUrl', - value: bid.native.clickUrl, - }); - expect(message.assets).to.deep.include({ - key: 'title', - value: bid.native.title, - }); - expect(message.assets).to.deep.include({ - key: 'icon', - value: bid.native.icon.url, - }); - expect(message.assets).to.deep.include({ - key: 'cta', - value: bid.native.cta, - }); - expect(message.assets).to.deep.include({ - key: 'sponsoredBy', - value: bid.native.sponsoredBy, - }); - expect(message.assets).to.deep.include({ - key: 'foo', - value: bid.native.ext.foo, - }); - expect(message.assets).to.deep.include({ - key: 'baz', - value: bid.native.ext.baz, - }); - }); - - it('creates native all asset message with only defined fields', function () { - const messageRequest = { - message: 'Prebid Native', - action: 'allAssetRequest', - adId: '123', - }; - - const message = getAllAssetsMessage(messageRequest, bidWithUndefinedFields); - - expect(message.assets.length).to.equal(4); - expect(message.assets).to.deep.include({ - key: 'clickUrl', - value: bid.native.clickUrl, - }); - expect(message.assets).to.deep.include({ - key: 'title', - value: bid.native.title, - }); - expect(message.assets).to.deep.include({ - key: 'sponsoredBy', - value: bid.native.sponsoredBy, - }); - expect(message.assets).to.deep.include({ - key: 'foo', - value: bid.native.ext.foo, - }); - }); - - it('creates native all asset message with complete format', function () { - const messageRequest = { - message: 'Prebid Native', - action: 'allAssetRequest', - adId: '123', - }; - - const message = getAllAssetsMessage(messageRequest, completeNativeBid); + Object.entries({ + 'returns native data': { + renderDataHook(next, bidResponse) { + next.bail({ + native: getNativeRenderingData(bidResponse, adUnit) + }); + }, + renderSourceHook(next) { + next.bail('mock-native-renderer'); + }, + withRenderer: true + }, + 'does not return native data': { + renderDataHook(next) { + next.bail({}) + }, + renderSourceHook(next) { + next.bail('mock-display-renderer'); + }, + withRenderer: false + } + }).forEach(([t, {renderDataHook, renderSourceHook, withRenderer}]) => { + describe(`when getRenderingData ${t}`, () => { + before(() => { + getRenderingData.before(renderDataHook, 100); + getCreativeRendererSource.before(renderSourceHook, 100); + }); + after(() => { + getRenderingData.getHooks({hook: renderDataHook}).remove(); + getCreativeRendererSource.getHooks({hook: renderSourceHook}).remove(); + }); + + function checkRenderer(message) { + if (withRenderer) { + expect(message.renderer).to.eql('mock-native-renderer') + Object.entries(message).forEach(([key, val]) => { + if (!['native', 'adId', 'message', 'assets', 'renderer'].includes(key)) { + expect(message.native[key]).to.eql(val); + } + }) + message.assets.forEach(asset => { + expect(message.native.assets).to.contain(asset); + }) + } else { + expect(message.renderer).to.not.exist; + expect(message.native).to.not.exist; + } + } - expect(message.assets.length).to.equal(10); - expect(message.assets).to.deep.include({ - key: 'body', - value: bid.native.body, - }); - expect(message.assets).to.deep.include({ - key: 'image', - value: bid.native.image.url, - }); - expect(message.assets).to.deep.include({ - key: 'clickUrl', - value: bid.native.clickUrl, - }); - expect(message.assets).to.deep.include({ - key: 'title', - value: bid.native.title, - }); - expect(message.assets).to.deep.include({ - key: 'icon', - value: bid.native.icon.url, - }); - expect(message.assets).to.deep.include({ - key: 'cta', - value: bid.native.cta, - }); - expect(message.assets).to.deep.include({ - key: 'sponsoredBy', - value: bid.native.sponsoredBy, - }); - expect(message.assets).to.deep.include({ - key: 'privacyLink', - value: ortbBid.native.ortb.privacy, - }); - expect(message.assets).to.deep.include({ - key: 'foo', - value: bid.native.ext.foo, - }); - expect(message.assets).to.deep.include({ - key: 'baz', - value: bid.native.ext.baz, + it('creates native asset message', function () { + const messageRequest = { + message: 'Prebid Native', + action: 'assetRequest', + adId: '123', + assets: ['hb_native_body', 'hb_native_image', 'hb_native_linkurl'], + }; + + const message = getAssetMessage(messageRequest, bid); + + expect(message.assets.length).to.equal(3); + expect(message.assets).to.deep.include({ + key: 'body', + value: bid.native.body, + }); + expect(message.assets).to.deep.include({ + key: 'image', + value: bid.native.image.url, + }); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl, + }); + checkRenderer(message); + }); + + it('creates native all asset message', function () { + const messageRequest = { + message: 'Prebid Native', + action: 'allAssetRequest', + adId: '123', + }; + + const message = getAllAssetsMessage(messageRequest, bid); + + expect(message.assets.length).to.equal(10); + expect(message.assets).to.deep.include({ + key: 'body', + value: bid.native.body, + }); + expect(message.assets).to.deep.include({ + key: 'image', + value: bid.native.image.url, + }); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl, + }); + expect(message.assets).to.deep.include({ + key: 'title', + value: bid.native.title, + }); + expect(message.assets).to.deep.include({ + key: 'icon', + value: bid.native.icon.url, + }); + expect(message.assets).to.deep.include({ + key: 'cta', + value: bid.native.cta, + }); + expect(message.assets).to.deep.include({ + key: 'sponsoredBy', + value: bid.native.sponsoredBy, + }); + expect(message.assets).to.deep.include({ + key: 'foo', + value: bid.native.ext.foo, + }); + expect(message.assets).to.deep.include({ + key: 'baz', + value: bid.native.ext.baz, + }); + checkRenderer(message); + }); + + it('creates native all asset message with only defined fields', function () { + const messageRequest = { + message: 'Prebid Native', + action: 'allAssetRequest', + adId: '123', + }; + + const message = getAllAssetsMessage(messageRequest, bidWithUndefinedFields); + + expect(message.assets.length).to.equal(4); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl, + }); + expect(message.assets).to.deep.include({ + key: 'title', + value: bid.native.title, + }); + expect(message.assets).to.deep.include({ + key: 'sponsoredBy', + value: bid.native.sponsoredBy, + }); + expect(message.assets).to.deep.include({ + key: 'foo', + value: bid.native.ext.foo, + }); + checkRenderer(message); + }); + + it('creates native all asset message with complete format', function () { + const messageRequest = { + message: 'Prebid Native', + action: 'allAssetRequest', + adId: '123', + }; + + const message = getAllAssetsMessage(messageRequest, completeNativeBid); + + expect(message.assets.length).to.equal(10); + expect(message.assets).to.deep.include({ + key: 'body', + value: bid.native.body, + }); + expect(message.assets).to.deep.include({ + key: 'image', + value: bid.native.image.url, + }); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl, + }); + expect(message.assets).to.deep.include({ + key: 'title', + value: bid.native.title, + }); + expect(message.assets).to.deep.include({ + key: 'icon', + value: bid.native.icon.url, + }); + expect(message.assets).to.deep.include({ + key: 'cta', + value: bid.native.cta, + }); + expect(message.assets).to.deep.include({ + key: 'sponsoredBy', + value: bid.native.sponsoredBy, + }); + expect(message.assets).to.deep.include({ + key: 'privacyLink', + value: ortbBid.native.ortb.privacy, + }); + expect(message.assets).to.deep.include({ + key: 'foo', + value: bid.native.ext.foo, + }); + expect(message.assets).to.deep.include({ + key: 'baz', + value: bid.native.ext.baz, + }); + checkRenderer(message); + }); + + it('if necessary, adds ortb response when the request was in ortb', () => { + const messageRequest = { + message: 'Prebid Native', + action: 'allAssetRequest', + adId: '123', + }; + adUnit = {mediaTypes: {native: {ortb: ortbRequest}}, nativeOrtbRequest: ortbRequest} + const message = getAllAssetsMessage(messageRequest, bid); + const expected = toOrtbNativeResponse(bid.native, ortbRequest) + expect(message.ortb).to.eql(expected); + checkRenderer(message); + }); }); }); - - it('if necessary, adds ortb response when the request was in ortb', () => { - const messageRequest = { - message: 'Prebid Native', - action: 'allAssetRequest', - adId: '123', - }; - adUnit = {mediaTypes: {native: {ortb: ortbRequest}}, nativeOrtbRequest: ortbRequest} - const message = getAllAssetsMessage(messageRequest, bid); - const expected = toOrtbNativeResponse(bid.native, ortbRequest) - expect(message.ortb).to.eql(expected); - }) - }) + }); const SAMPLE_ORTB_REQUEST = toOrtbNativeRequest({ title: 'vtitle',