diff --git a/modules/1plusXRtdProvider.js b/modules/1plusXRtdProvider.js index 390f4f4cd81..665a0f2b35e 100644 --- a/modules/1plusXRtdProvider.js +++ b/modules/1plusXRtdProvider.js @@ -1,10 +1,9 @@ import { submodule } from '../src/hook.js'; -import { config } from '../src/config.js'; import { ajax } from '../src/ajax.js'; import { logMessage, logError, - deepAccess, mergeDeep, - isNumber, isArray, deepSetValue + deepAccess, deepSetValue, mergeDeep, + isNumber, isArray, } from '../src/utils.js'; // Constants @@ -14,7 +13,6 @@ const ORTB2_NAME = '1plusX.com' const PAPI_VERSION = 'v1.0'; const LOG_PREFIX = '[1plusX RTD Module]: '; const OPE_FPID = 'ope_fpid' -const LEGACY_SITE_KEYWORDS_BIDDERS = ['appnexus']; export const segtaxes = { // cf. https://github.com/InteractiveAdvertisingBureau/openrtb/pull/108 AUDIENCE: 526, @@ -151,18 +149,7 @@ const getTargetingDataFromPapi = (papiUrl) => { * @param {string[]} topics Represents the topics of the page * @returns {Object} Object describing the updates to make on bidder configs */ -export const buildOrtb2Updates = ({ segments = [], topics = [] }, bidder) => { - // Currently appnexus bidAdapter doesn't support topics in `site.content.data.segment` - // Therefore, writing them in `site.keywords` until it's supported - // Other bidAdapters do fine with `site.content.data.segment` - const writeToLegacySiteKeywords = LEGACY_SITE_KEYWORDS_BIDDERS.includes(bidder); - if (writeToLegacySiteKeywords) { - const site = { - keywords: topics.join(',') - }; - return { site }; - } - +export const buildOrtb2Updates = ({ segments = [], topics = [] }) => { const userData = { name: ORTB2_NAME, segment: segments.map((segmentId) => ({ id: segmentId })) @@ -172,81 +159,64 @@ export const buildOrtb2Updates = ({ segments = [], topics = [] }, bidder) => { segment: topics.map((topicId) => ({ id: topicId })), ext: { segtax: segtaxes.CONTENT } } - return { userData, siteContentData }; + // Currently appnexus bidAdapter doesn't support topics in `site.content.data.segment` + // Therefore, writing them in `site.keywords` until it's supported + // Other bidAdapters do fine with `site.content.data.segment` + const siteKeywords = topics.map(topic => `1plusX=${topic}`).join(','); + + return { userData, siteContentData, siteKeywords }; } /** * Merges the targeting data with the existing config for bidder and updates * @param {string} bidder Bidder for which to set config * @param {Object} ortb2Updates Updates to be applied to bidder config - * @param {Object} bidderConfigs All current bidder configs - * @returns {Object} Updated bidder config + * @param {Object} biddersOrtb2 All current bidder configs */ -export const updateBidderConfig = (bidder, ortb2Updates, bidderConfigs) => { - const { site, siteContentData, userData } = ortb2Updates; - const bidderConfigCopy = mergeDeep({}, bidderConfigs[bidder]); +export const updateBidderConfig = (bidder, ortb2Updates, biddersOrtb2) => { + const { siteKeywords, siteContentData, userData } = ortb2Updates; + mergeDeep(biddersOrtb2, { [bidder]: {} }); + const bidderConfig = deepAccess(biddersOrtb2, bidder); - if (site) { - // Legacy : cf. comment on buildOrtb2Updates first lines - const currentSite = deepAccess(bidderConfigCopy, 'ortb2.site'); - const updatedSite = mergeDeep(currentSite, site); - deepSetValue(bidderConfigCopy, 'ortb2.site', updatedSite); + { + // Legacy : cf. comment on buildOrtb2Updates + const siteKeywordsPath = 'site.keywords'; + deepSetValue(bidderConfig, siteKeywordsPath, siteKeywords); } - if (siteContentData) { - const siteDataPath = 'ortb2.site.content.data'; - const currentSiteContentData = deepAccess(bidderConfigCopy, siteDataPath) || []; + { + const siteDataPath = 'site.content.data'; + const currentSiteContentData = deepAccess(bidderConfig, siteDataPath) || []; const updatedSiteContentData = [ ...currentSiteContentData.filter(({ name }) => name != siteContentData.name), siteContentData ]; - deepSetValue(bidderConfigCopy, siteDataPath, updatedSiteContentData); + deepSetValue(bidderConfig, siteDataPath, updatedSiteContentData); } - if (userData) { - const userDataPath = 'ortb2.user.data'; - const currentUserData = deepAccess(bidderConfigCopy, userDataPath) || []; + { + const userDataPath = 'user.data'; + const currentUserData = deepAccess(bidderConfig, userDataPath) || []; const updatedUserData = [ ...currentUserData.filter(({ name }) => name != userData.name), userData ]; - deepSetValue(bidderConfigCopy, userDataPath, updatedUserData); + deepSetValue(bidderConfig, userDataPath, updatedUserData); } - - return bidderConfigCopy; }; -const setAppnexusAudiences = (audiences) => { - config.setConfig({ - appnexusAuctionKeywords: { - '1plusX': audiences, - }, - }); -} - /** * Updates bidder configs with the targeting data retreived from Profile API * @param {Object} papiResponse Response from Profile API * @param {Object} config Module configuration * @param {string[]} config.bidders Bidders specified in module's configuration */ -export const setTargetingDataToConfig = (papiResponse, { bidders }) => { - const bidderConfigs = config.getBidderConfig(); +export const setTargetingDataToConfig = (papiResponse, { bidders, biddersOrtb2 }) => { const { s: segments, t: topics } = papiResponse; + const ortb2Updates = buildOrtb2Updates({ segments, topics }); for (const bidder of bidders) { - const ortb2Updates = buildOrtb2Updates({ segments, topics }, bidder); - const updatedBidderConfig = updateBidderConfig(bidder, ortb2Updates, bidderConfigs); - if (updatedBidderConfig) { - config.setBidderConfig({ - bidders: [bidder], - config: updatedBidderConfig - }); - } - if (bidder === 'appnexus') { - // Do the legacy stuff for appnexus with segments - setAppnexusAudiences(segments); - } + updateBidderConfig(bidder, ortb2Updates, biddersOrtb2); } } @@ -272,13 +242,14 @@ const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, userConsent try { // Get the required config const { customerId, bidders } = extractConfig(moduleConfig, reqBidsConfigObj); + const { ortb2Fragments: { bidder: biddersOrtb2 } } = reqBidsConfigObj; // Get PAPI URL const papiUrl = getPapiUrl(customerId, extractConsent(userConsent) || {}, extractFpid()) // Call PAPI getTargetingDataFromPapi(papiUrl) .then((papiResponse) => { logMessage(LOG_PREFIX, 'Get targeting data request successful'); - setTargetingDataToConfig(papiResponse, { bidders }); + setTargetingDataToConfig(papiResponse, { bidders, biddersOrtb2 }); callback(); }) } catch (error) { diff --git a/test/spec/modules/1plusXRtdProvider_spec.js b/test/spec/modules/1plusXRtdProvider_spec.js index 8657c37d7d8..66d0a74c194 100644 --- a/test/spec/modules/1plusXRtdProvider_spec.js +++ b/test/spec/modules/1plusXRtdProvider_spec.js @@ -1,16 +1,16 @@ +import assert from 'assert'; import { config } from 'src/config'; import { onePlusXSubmodule, - segtaxes, - extractConfig, buildOrtb2Updates, - updateBidderConfig, - setTargetingDataToConfig, + extractConfig, extractConsent, - getPapiUrl + extractFpid, + getPapiUrl, + segtaxes, + setTargetingDataToConfig, + updateBidderConfig, } from 'modules/1plusXRtdProvider'; -import assert from 'assert'; -import { extractFpid } from '../../../modules/1plusXRtdProvider'; describe('1plusXRtdProvider', () => { // Fake server config @@ -35,47 +35,37 @@ describe('1plusXRtdProvider', () => { // Bidder configs const bidderConfigInitial = { - ortb2: { - user: { keywords: '' }, - site: { ext: {} } - } + user: { keywords: '' }, + site: { ext: {} } } const bidderConfigInitialWith1plusXUserData = { - ortb2: { - user: { - data: [{ name: '1plusX.com', segment: [{ id: 'initial' }] }] - }, - site: { content: { data: [] } } - } + user: { + data: [{ name: '1plusX.com', segment: [{ id: 'initial' }] }] + }, + site: { content: { data: [] } } } const bidderConfigInitialWithUserData = { - ortb2: { - user: { - data: [{ name: 'hello.world', segment: [{ id: 'initial' }] }] - }, - site: { content: { data: [] } } - } + user: { + data: [{ name: 'hello.world', segment: [{ id: 'initial' }] }] + }, + site: { content: { data: [] } } } const bidderConfigInitialWith1plusXSiteContent = { - ortb2: { - user: { data: [] }, - site: { - content: { - data: [{ - name: '1plusX.com', segment: [{ id: 'initial' }], ext: { segtax: 525 } - }] - } - }, + user: { data: [] }, + site: { + content: { + data: [{ + name: '1plusX.com', segment: [{ id: 'initial' }], ext: { segtax: 525 } + }] + } } } const bidderConfigInitialWithSiteContent = { - ortb2: { - user: { data: [] }, - site: { - content: { - data: [{ name: 'hello.world', segment: [{ id: 'initial' }] }] - } - }, + user: { data: [] }, + site: { + content: { + data: [{ name: 'hello.world', segment: [{ id: 'initial' }] }] + } } } // Util functions @@ -178,7 +168,7 @@ describe('1plusXRtdProvider', () => { }) describe('buildOrtb2Updates', () => { - it('fills site.content.data & user.data in the ortb2 config', () => { + it('fills site.content.data, user.data & site.keywords in the ortb2 config', () => { const rtdData = { segments: fakeResponse.s, topics: fakeResponse.t }; const ortb2Updates = buildOrtb2Updates(rtdData, randomBidder()); @@ -191,18 +181,8 @@ describe('1plusXRtdProvider', () => { userData: { name: '1plusX.com', segment: rtdData.segments.map((segmentId) => ({ id: segmentId })) - } - } - expect([ortb2Updates]).to.deep.include.members([expectedOutput]); - }); - it('fills site.keywords in the ortb2 config (appnexus specific)', () => { - const rtdData = { segments: fakeResponse.s, topics: fakeResponse.t }; - const ortb2Updates = buildOrtb2Updates(rtdData, 'appnexus'); - - const expectedOutput = { - site: { - keywords: rtdData.topics.join(','), - } + }, + siteKeywords: rtdData.topics.map(topic => `1plusX=${topic}`).join(','), } expect([ortb2Updates]).to.deep.include.members([expectedOutput]); }); @@ -220,7 +200,8 @@ describe('1plusXRtdProvider', () => { userData: { name: '1plusX.com', segment: [] - } + }, + siteKeywords: rtdData.topics.map(topic => `1plusX=${topic}`).join(','), } expect(ortb2Updates).to.deep.include(expectedOutput); }) @@ -238,20 +219,10 @@ describe('1plusXRtdProvider', () => { userData: { name: '1plusX.com', segment: rtdData.segments.map((segmentId) => ({ id: segmentId })) - } - } - expect(ortb2Updates).to.deep.include(expectedOutput); - }) - it('defaults to empty string if no topic is given (appnexus specific)', () => { - const rtdData = { segments: fakeResponse.s }; - const ortb2Updates = buildOrtb2Updates(rtdData, 'appnexus'); - - const expectedOutput = { - site: { - keywords: '', - } + }, + siteKeywords: '', } - expect(ortb2Updates).to.deep.include(expectedOutput); + expect(ortb2Updates, `${JSON.stringify(ortb2Updates, null, 2)}`).to.deep.include(expectedOutput); }) }) @@ -271,6 +242,7 @@ describe('1plusXRtdProvider', () => { expect(expectedOutput).to.deep.include(output) expect(output).to.deep.include(expectedOutput) }) + it('extracts null if consent object is empty', () => { const consent1 = {} expect(extractConsent(consent1)).to.equal(null) @@ -345,15 +317,6 @@ describe('1plusXRtdProvider', () => { }) describe('updateBidderConfig', () => { - const ortb2UpdatesAppNexus = { - site: { - keywords: fakeResponse.t.join(','), - }, - userData: { - name: '1plusX.com', - segment: fakeResponse.s.map((segmentId) => ({ id: segmentId })) - } - } const ortb2Updates = { siteContentData: { name: '1plusX.com', @@ -363,114 +326,98 @@ describe('1plusXRtdProvider', () => { userData: { name: '1plusX.com', segment: fakeResponse.s.map((segmentId) => ({ id: segmentId })) - } + }, + siteKeywords: fakeResponse.t.map(topic => `1plusX=${topic}`).join(','), } it('merges fetched data in bidderConfig for configured bidders', () => { - const bidder = randomBidder(); - // Set initial config - config.setBidderConfig({ - bidders: [bidder], - config: bidderConfigInitial - }); - // Call submodule's setBidderConfig - const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); - // Check that the targeting data has been set in the config - expect(newBidderConfig).not.to.be.null; - expect(newBidderConfig.ortb2.site.content.data).to.deep.include(ortb2Updates.siteContentData); - expect(newBidderConfig.ortb2.user.data).to.deep.include(ortb2Updates.userData); - // Check that existing config didn't get erased - expect(newBidderConfig.ortb2.site).to.deep.include(bidderConfigInitial.ortb2.site); - expect(newBidderConfig.ortb2.user).to.deep.include(bidderConfigInitial.ortb2.user); - }) - - it('merges fetched data in bidderConfig for configured bidders (appnexus specific)', () => { - const bidder = 'appnexus'; // Set initial config - config.setBidderConfig({ - bidders: [bidder], - config: bidderConfigInitial - }); + const bidder = randomBidder(); + const ortb2Fragments = { [bidder]: { ...bidderConfigInitial } } // Call submodule's setBidderConfig - const newBidderConfig = updateBidderConfig(bidder, ortb2UpdatesAppNexus, config.getBidderConfig()); - + updateBidderConfig(bidder, ortb2Updates, ortb2Fragments); + const newBidderConfig = ortb2Fragments[bidder]; // Check that the targeting data has been set in the config - expect(newBidderConfig).not.to.be.null; - expect(newBidderConfig.ortb2.site).to.deep.include(ortb2UpdatesAppNexus.site); + expect(newBidderConfig).not.to.be.null.and.not.to.be.undefined; + expect(newBidderConfig.user).not.to.be.null.and.not.to.be.undefined; + expect(newBidderConfig.site).not.to.be.null.and.not.to.be.undefined; + expect(newBidderConfig.user.data).to.deep.include(ortb2Updates.userData); + expect(newBidderConfig.site.keywords).to.deep.include(ortb2Updates.siteKeywords); + expect(newBidderConfig.site.content.data).to.deep.include(ortb2Updates.siteContentData); // Check that existing config didn't get erased - expect(newBidderConfig.ortb2.site).to.deep.include(bidderConfigInitial.ortb2.site); - expect(newBidderConfig.ortb2.user).to.deep.include(bidderConfigInitial.ortb2.user); + expect(newBidderConfig.site).to.deep.include(bidderConfigInitial.site); + expect(newBidderConfig.user).to.deep.include(bidderConfigInitial.user); }) it('overwrites an existing 1plus.com entry in ortb2.user.data', () => { - const bidder = randomBidder(); // Set initial config - config.setBidderConfig({ - bidders: [bidder], - config: bidderConfigInitialWith1plusXUserData - }); + const bidder = randomBidder(); + const ortb2Fragments = { [bidder]: { ...bidderConfigInitialWith1plusXUserData } } // Save previous user.data entry - const previousUserData = bidderConfigInitialWith1plusXUserData.ortb2.user.data[0]; + const previousUserData = bidderConfigInitialWith1plusXUserData.user.data[0]; // Call submodule's setBidderConfig - const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); + updateBidderConfig(bidder, ortb2Updates, ortb2Fragments); + const newBidderConfig = ortb2Fragments[bidder]; // Check that the targeting data has been set in the config expect(newBidderConfig).not.to.be.null; - expect(newBidderConfig.ortb2.user.data).to.deep.include(ortb2Updates.userData); - expect(newBidderConfig.ortb2.user.data).not.to.include(previousUserData); + expect(newBidderConfig.site).not.to.be.null; + expect(newBidderConfig.user).not.to.be.null; + expect(newBidderConfig.user.data).to.deep.include(ortb2Updates.userData); + expect(newBidderConfig.user.data).not.to.include(previousUserData); }) it("doesn't overwrite entries in ortb2.user.data that aren't 1plusx.com", () => { - const bidder = randomBidder(); // Set initial config - config.setBidderConfig({ - bidders: [bidder], - config: bidderConfigInitialWithUserData - }); + const bidder = randomBidder(); + const ortb2Fragments = { [bidder]: { ...bidderConfigInitialWithUserData } } // Save previous user.data entry - const previousUserData = bidderConfigInitialWithUserData.ortb2.user.data[0]; + const previousUserData = bidderConfigInitialWithUserData.user.data[0]; // Call submodule's setBidderConfig - const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); + updateBidderConfig(bidder, ortb2Updates, ortb2Fragments); + const newBidderConfig = ortb2Fragments[bidder]; // Check that the targeting data has been set in the config expect(newBidderConfig).not.to.be.null; - expect(newBidderConfig.ortb2.user.data).to.deep.include(ortb2Updates.userData); - expect(newBidderConfig.ortb2.user.data).to.deep.include(previousUserData); + expect(newBidderConfig.site).not.to.be.null; + expect(newBidderConfig.user).not.to.be.null; + expect(newBidderConfig.user.data).to.deep.include(ortb2Updates.userData); + expect(newBidderConfig.user.data).to.deep.include(previousUserData); }) it('overwrites an existing 1plus.com entry in ortb2.site.content.data', () => { - const bidder = randomBidder(); // Set initial config - config.setBidderConfig({ - bidders: [bidder], - config: bidderConfigInitialWith1plusXSiteContent - }); + const bidder = randomBidder(); + const ortb2Fragments = { [bidder]: { ...bidderConfigInitialWith1plusXSiteContent } } // Save previous user.data entry - const previousSiteContent = bidderConfigInitialWith1plusXSiteContent.ortb2.site.content.data[0]; + const previousSiteContent = bidderConfigInitialWith1plusXSiteContent.site.content.data[0]; // Call submodule's setBidderConfig - const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); + updateBidderConfig(bidder, ortb2Updates, ortb2Fragments); + const newBidderConfig = ortb2Fragments[bidder]; // Check that the targeting data has been set in the config expect(newBidderConfig).not.to.be.null; - expect(newBidderConfig.ortb2.site.content.data).to.deep.include(ortb2Updates.siteContentData); - expect(newBidderConfig.ortb2.site.content.data).not.to.include(previousSiteContent); + expect(newBidderConfig.site).not.to.be.null; + expect(newBidderConfig.user).not.to.be.null; + expect(newBidderConfig.site.content.data).to.deep.include(ortb2Updates.siteContentData); + expect(newBidderConfig.site.content.data).not.to.include(previousSiteContent); }) it("doesn't overwrite entries in ortb2.site.content.data that aren't 1plusx.com", () => { - const bidder = randomBidder(); // Set initial config - config.setBidderConfig({ - bidders: [bidder], - config: bidderConfigInitialWithSiteContent - }); + const bidder = randomBidder(); + const ortb2Fragments = { [bidder]: { ...bidderConfigInitialWithSiteContent } } // Save previous user.data entry - const previousSiteContent = bidderConfigInitialWithSiteContent.ortb2.site.content.data[0]; + const previousSiteContent = bidderConfigInitialWithSiteContent.site.content.data[0]; // Call submodule's setBidderConfig - const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); + updateBidderConfig(bidder, ortb2Updates, ortb2Fragments); + const newBidderConfig = ortb2Fragments[bidder]; // Check that the targeting data has been set in the config expect(newBidderConfig).not.to.be.null; - expect(newBidderConfig.ortb2.site.content.data).to.deep.include(ortb2Updates.siteContentData); - expect(newBidderConfig.ortb2.site.content.data).to.deep.include(previousSiteContent); + expect(newBidderConfig.site).not.to.be.null; + expect(newBidderConfig.user).not.to.be.null; + expect(newBidderConfig.site.content.data).to.deep.include(ortb2Updates.siteContentData); + expect(newBidderConfig.site.content.data).to.deep.include(previousSiteContent); }) }) describe('setTargetingDataToConfig', () => { - const expectedKeywords = fakeResponse.t.join(','); + const expectedKeywords = fakeResponse.t.map(topic => `1plusX=${topic}`).join(','); const expectedSiteContentObj = { data: [{ name: '1plusX.com', @@ -486,10 +433,11 @@ describe('1plusXRtdProvider', () => { } const expectedOrtb2 = { appnexus: { - site: { keywords: expectedKeywords } + site: { content: expectedSiteContentObj, keywords: expectedKeywords }, + user: expectedUserObj }, rubicon: { - site: { content: expectedSiteContentObj }, + site: { content: expectedSiteContentObj, keywords: expectedKeywords }, user: expectedUserObj } } @@ -501,22 +449,23 @@ describe('1plusXRtdProvider', () => { bidders, config: bidderConfigInitial }) + const biddersOrtb2 = config.getBidderConfig(); // call setTargetingDataToConfig - setTargetingDataToConfig(fakeResponse, { bidders }); + setTargetingDataToConfig(fakeResponse, { bidders, biddersOrtb2 }); // Check that the targeting data has been set in both configs for (const bidder of bidders) { const newConfig = config.getBidderConfig()[bidder]; // Check that we got what we expect const expectedConfErr = (prop) => `New config for ${bidder} doesn't comply with expected at ${prop}`; - expect(newConfig.ortb2.site, expectedConfErr('site')).to.deep.include(expectedOrtb2[bidder].site); + expect(newConfig.site, expectedConfErr('site')).to.deep.include(expectedOrtb2[bidder].site); if (expectedOrtb2[bidder].user) { - expect(newConfig.ortb2.user, expectedConfErr('user')).to.deep.include(expectedOrtb2[bidder].user); + expect(newConfig.user, expectedConfErr('user')).to.deep.include(expectedOrtb2[bidder].user); } // Check that existing config didn't get erased const existingConfErr = (prop) => `Existing config for ${bidder} got unlawfully overwritten at ${prop}`; - expect(newConfig.ortb2.site, existingConfErr('site')).to.deep.include(bidderConfigInitial.ortb2.site); - expect(newConfig.ortb2.user, existingConfErr('user')).to.deep.include(bidderConfigInitial.ortb2.user); + expect(newConfig.site, existingConfErr('site')).to.deep.include(bidderConfigInitial.site); + expect(newConfig.user, existingConfErr('user')).to.deep.include(bidderConfigInitial.user); } }) })