From 8ddef8fdb677c9b0fc1ff8f35a57de4f18794602 Mon Sep 17 00:00:00 2001 From: Scott Menzer Date: Mon, 18 Jan 2021 14:16:28 +0100 Subject: [PATCH 1/7] ID5 User Id Module: move id5's user id extension to the uids array (#6193) * move id5's user id extension to the uids array to better conform to conventions * update location of id5 ext in spotx bid adapter --- modules/rubiconBidAdapter.js | 2 +- modules/spotxBidAdapter.js | 6 +++--- modules/userId/eids.js | 2 +- modules/userId/eids.md | 10 +++++----- test/spec/modules/eids_spec.js | 11 +++++++---- test/spec/modules/id5IdSystem_spec.js | 11 +++++++---- test/spec/modules/nobidBidAdapter_spec.js | 12 ++++++------ test/spec/modules/prebidServerBidAdapter_spec.js | 2 +- test/spec/modules/spotxBidAdapter_spec.js | 6 +++--- 9 files changed, 34 insertions(+), 28 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index e439f7fd2a4..2981bd4f3fa 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -516,7 +516,7 @@ export const spec = { } else if (eid.source === 'sharedid.org') { data['eid_sharedid.org'] = `${eid.uids[0].id}^${eid.uids[0].atype}^${(eid.uids[0].ext && eid.uids[0].ext.third) || ''}`; } else if (eid.source === 'id5-sync.com') { - data['eid_id5-sync.com'] = `${eid.uids[0].id}^${eid.uids[0].atype}^${(eid.ext && eid.ext.linkType) || ''}`; + data['eid_id5-sync.com'] = `${eid.uids[0].id}^${eid.uids[0].atype}^${(eid.uids[0].ext && eid.uids[0].ext.linkType) || ''}`; } else { // add anything else with this generic format data[`eid_${eid.source}`] = `${eid.uids[0].id}^${eid.uids[0].atype || ''}`; diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index 6104fce1d97..5ed3884e977 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -245,9 +245,9 @@ export const spec = { { source: 'id5-sync.com', uids: [{ - id: bid.userId.id5id.uid - }], - ext: bid.userId.id5id.ext || {} + id: bid.userId.id5id.uid, + ext: bid.userId.id5id.ext || {} + }] } ) } diff --git a/modules/userId/eids.js b/modules/userId/eids.js index d714d09962d..65fa159502c 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -35,7 +35,7 @@ const USER_IDS_CONFIG = { }, source: 'id5-sync.com', atype: 1, - getEidExt: function(data) { + getUidExt: function(data) { if (data.ext) { return data.ext; } diff --git a/modules/userId/eids.md b/modules/userId/eids.md index fecf7e888bf..a189c257ba9 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -33,11 +33,11 @@ userIdAsEids = [ source: 'id5-sync.com', uids: [{ id: 'some-random-id-value', - atype: 1 - }, - ext: { - linkType: 2, - abTestingControlGroup: false + atype: 1, + ext: { + linkType: 2, + abTestingControlGroup: false + } }] }, diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 26dbad2f153..61fa0e846a5 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -57,10 +57,13 @@ describe('eids array generation for known sub-modules', function() { expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ source: 'id5-sync.com', - uids: [{ id: 'some-random-id-value', atype: 1 }], - ext: { - linkType: 0 - } + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + linkType: 0 + } + }] }); }); }); diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 1b13059a7f8..24f50db9980 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -297,10 +297,13 @@ describe('ID5 ID System', function() { expect(bid.userId.id5id.uid).to.equal(ID5_STORED_ID); expect(bid.userIdAsEids[0]).to.deep.equal({ source: ID5_SOURCE, - uids: [{ id: ID5_STORED_ID, atype: 1 }], - ext: { - linkType: ID5_STORED_LINK_TYPE - } + uids: [{ + id: ID5_STORED_ID, + atype: 1, + ext: { + linkType: ID5_STORED_LINK_TYPE + } + }] }); }); }); diff --git a/test/spec/modules/nobidBidAdapter_spec.js b/test/spec/modules/nobidBidAdapter_spec.js index e67d3b41f1d..8dc15fbc89e 100644 --- a/test/spec/modules/nobidBidAdapter_spec.js +++ b/test/spec/modules/nobidBidAdapter_spec.js @@ -257,12 +257,12 @@ describe('Nobid Adapter', function () { 'uids': [ { 'id': 'ID5_ID', - 'atype': 1 + 'atype': 1, + 'ext': { + 'linkType': 0 + } } - ], - 'ext': { - 'linkType': 0 - } + ] }, { 'source': 'adserver.org', @@ -284,7 +284,7 @@ describe('Nobid Adapter', function () { refererInfo: {referer: REFERER} } - it('should criteo eid', function () { + it('should get user ids from eids', function () { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(request.data); expect(payload.sid).to.exist.and.to.equal(2); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 9b17875c117..17c0fc290c6 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1268,7 +1268,7 @@ describe('S2S Adapter', function () { expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveintent.com')[0].ext.segments[1]).is.equal('segB'); expect(requestBid.user.ext.eids.filter(eid => eid.source === 'id5-sync.com')).is.not.empty; expect(requestBid.user.ext.eids.filter(eid => eid.source === 'id5-sync.com')[0].uids[0].id).is.equal('11111'); - expect(requestBid.user.ext.eids.filter(eid => eid.source === 'id5-sync.com')[0].ext.linkType).is.equal('some-link-type'); + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'id5-sync.com')[0].uids[0].ext.linkType).is.equal('some-link-type'); // LiveRamp should exist expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveramp.com')[0].uids[0].id).is.equal('0000-1111-2222-3333'); }); diff --git a/test/spec/modules/spotxBidAdapter_spec.js b/test/spec/modules/spotxBidAdapter_spec.js index 798fb3eec10..fb5ce63f543 100644 --- a/test/spec/modules/spotxBidAdapter_spec.js +++ b/test/spec/modules/spotxBidAdapter_spec.js @@ -204,9 +204,9 @@ describe('the spotx adapter', function () { eids: [{ source: 'id5-sync.com', uids: [{ - id: 'id5id_1' - }], - ext: {} + id: 'id5id_1', + ext: {} + }] }, { source: 'adserver.org', From 2f82760da91f15ec8ad2f8e91d60ba345065fb28 Mon Sep 17 00:00:00 2001 From: Maurice Roach Date: Mon, 18 Jan 2021 10:34:03 -0500 Subject: [PATCH 2/7] Add tapad id submodule (#6167) * Add tapad id submodule * clean and add specs --- modules/.submodules.json | 3 +- modules/tapadIdSystem.js | 61 +++++++++++++++++++++++ modules/userId/eids.js | 4 ++ modules/userId/eids.md | 7 +++ test/spec/modules/eids_spec.js | 12 +++++ test/spec/modules/tapadIdSystem_spec.js | 65 +++++++++++++++++++++++++ test/spec/modules/userId_spec.js | 58 ++++++++++++++++------ 7 files changed, 195 insertions(+), 15 deletions(-) create mode 100644 modules/tapadIdSystem.js create mode 100644 test/spec/modules/tapadIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 4e02391129a..fc69dd276a3 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -19,7 +19,8 @@ "idxIdSystem", "fabrickIdSystem", "verizonMediaIdSystem", - "pubProvidedIdSystem" + "pubProvidedIdSystem", + "tapadIdSystem" ], "adpod": [ "freeWheelAdserverVideo", diff --git a/modules/tapadIdSystem.js b/modules/tapadIdSystem.js new file mode 100644 index 00000000000..e4dfe304585 --- /dev/null +++ b/modules/tapadIdSystem.js @@ -0,0 +1,61 @@ +import { uspDataHandler } from '../src/adapterManager.js'; +import { submodule } from '../src/hook.js'; +import * as ajax from '../src/ajax.js' +import * as utils from '../src/utils.js'; + +export const graphUrl = 'https://realtime-graph-access-zxvhwknfeq-uc.a.run.app/v1/graph'; + +export const tapadIdSubmodule = { + name: 'tapadId', + /** + * decode the stored id value for passing to bid requests + * @function + * @returns {{tapadId: string} | undefined} + */ + decode(id) { + return { tapadId: id }; + }, + /* + * @function + * @summary initiate Real Time Graph + * @param {SubmoduleParams} [configParams] + * @param {ConsentData} [consentData] + * @returns {IdResponse }} + */ + getId(config) { + const uspData = uspDataHandler.getConsentData(); + if (uspData && uspData !== '1---') { + return { id: undefined }; + } + const configParams = config.params || {}; + + if (configParams.companyId == null || isNaN(Number(configParams.companyId))) { + utils.logMessage('Please provide a valid Company Id. Contact prebid@tapad.com for assistance.'); + } + + return { + callback: (complete) => { + ajax.ajaxBuilder(10000)( + `${graphUrl}?company_id=${configParams.companyId}&tapad_id_type=TAPAD_ID`, + { + success: (response) => { + const responseJson = JSON.parse(response); + if (responseJson.hasOwnProperty('tapadId')) { + complete(responseJson.tapadId); + } + }, + error: (_, e) => { + if (e.status === 404) { + complete(undefined); + } + if (e.status === 403) { + utils.logMessage('Invalid Company Id. Contact prebid@tapad.com for assistance.'); + } + } + } + ); + } + } + } +} +submodule('userId', tapadIdSubmodule); diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 65fa159502c..27665de4136 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -168,6 +168,10 @@ const USER_IDS_CONFIG = { 'fabrickId': { source: 'neustar.biz', atype: 1 + }, + 'tapadId': { + source: 'tapad.com', + atype: 1 } }; diff --git a/modules/userId/eids.md b/modules/userId/eids.md index a189c257ba9..b69c4b9bd5e 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -141,6 +141,13 @@ userIdAsEids = [ id: 'some-random-id-value', atype: 1 }] + }, + { + source: 'tapad.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] } ] ``` diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 61fa0e846a5..659e5257e90 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -172,6 +172,18 @@ describe('eids array generation for known sub-modules', function() { }); }); + it('tapadId', function() { + const userId = { + tapadId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'tapad.com', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + it('NetId', function() { const userId = { netId: 'some-random-id-value' diff --git a/test/spec/modules/tapadIdSystem_spec.js b/test/spec/modules/tapadIdSystem_spec.js new file mode 100644 index 00000000000..bc31f1d37ba --- /dev/null +++ b/test/spec/modules/tapadIdSystem_spec.js @@ -0,0 +1,65 @@ +import { tapadIdSubmodule, graphUrl } from 'modules/tapadIdSystem.js'; +import * as utils from 'src/utils.js'; + +import { server } from 'test/mocks/xhr.js'; + +describe('TapadIdSystem', function () { + describe('getId', function() { + const config = { params: { companyId: 12345 } }; + it('should call to real time graph endpoint and handle valid response', function() { + const callbackSpy = sinon.spy(); + const callback = tapadIdSubmodule.getId(config).callback; + callback(callbackSpy); + + const request = server.requests[0]; + expect(request.url).to.eq(`${graphUrl}?company_id=12345&tapad_id_type=TAPAD_ID`); + + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ tapadId: 'your-tapad-id' })); + expect(callbackSpy.lastCall.lastArg).to.eq('your-tapad-id'); + }); + + it('should remove stored tapadId if not found', function() { + const callbackSpy = sinon.spy(); + const callback = tapadIdSubmodule.getId(config).callback; + callback(callbackSpy); + + const request = server.requests[0]; + + request.respond(404); + expect(callbackSpy.lastCall.lastArg).to.be.undefined; + }); + + it('should log message with invalid company id', function() { + const logMessageSpy = sinon.spy(utils, 'logMessage'); + const callbackSpy = sinon.spy(); + const callback = tapadIdSubmodule.getId(config).callback; + callback(callbackSpy); + + const request = server.requests[0]; + + request.respond(403); + expect(logMessageSpy.lastCall.lastArg).to.eq('Invalid Company Id. Contact prebid@tapad.com for assistance.'); + logMessageSpy.restore(); + }); + + it('should log message if company id not given', function() { + const logMessageSpy = sinon.spy(utils, 'logMessage'); + const callbackSpy = sinon.spy(); + const callback = tapadIdSubmodule.getId({}).callback; + callback(callbackSpy); + + expect(logMessageSpy.lastCall.lastArg).to.eq('Please provide a valid Company Id. Contact prebid@tapad.com for assistance.'); + logMessageSpy.restore(); + }); + + it('should log message if company id is incorrect format', function() { + const logMessageSpy = sinon.spy(utils, 'logMessage'); + const callbackSpy = sinon.spy(); + const callback = tapadIdSubmodule.getId({ params: { companyId: 'notANumber' } }).callback; + callback(callbackSpy); + + expect(logMessageSpy.lastCall.lastArg).to.eq('Please provide a valid Company Id. Contact prebid@tapad.com for assistance.'); + logMessageSpy.restore(); + }); + }); +}) diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 9ada17d7460..ed592c0cba5 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -38,6 +38,7 @@ import {sharedIdSubmodule} from 'modules/sharedIdSystem.js'; import {haloIdSubmodule} from 'modules/haloIdSystem.js'; import {pubProvidedIdSubmodule} from 'modules/pubProvidedIdSystem.js'; import {criteoIdSubmodule} from 'modules/criteoIdSystem.js'; +import {tapadIdSubmodule} from 'modules/tapadIdSystem.js'; let assert = require('chai').assert; let expect = require('chai').expect; @@ -456,7 +457,7 @@ describe('User ID', function () { }); it('handles config with no usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); init(config); config.setConfig({}); // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' @@ -464,14 +465,14 @@ describe('User ID', function () { }); it('handles config with empty usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); init(config); config.setConfig({userSync: {}}); expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); it('handles config with usersync and userIds that are empty objs', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -482,7 +483,7 @@ describe('User ID', function () { }); it('handles config with usersync and userIds with empty names or that dont match a submodule.name', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -499,15 +500,15 @@ describe('User ID', function () { }); it('config with 1 configurations should create 1 submodules', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); }); - it('config with 13 configurations should result in 13 submodules add', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); + it('config with 14 configurations should result in 14 submodules add', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -547,14 +548,17 @@ describe('User ID', function () { name: 'zeotapIdPlus' }, { name: 'criteo' + }, { + name: 'tapadId', + storage: {name: 'tapad_id', type: 'cookie'} }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 13 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 14 submodules'); }); it('config syncDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -569,7 +573,7 @@ describe('User ID', function () { }); it('config auctionDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -584,7 +588,7 @@ describe('User ID', function () { }); it('config auctionDelay defaults to 0 if not a number', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -986,6 +990,29 @@ describe('User ID', function () { }, {adUnits}); }); + it('test hook from tapadIdModule cookie', function (done) { + coreStorage.setCookie('tapad_id', 'test-tapad-id', (new Date(Date.now() + 100000).toUTCString())); + + setSubmoduleRegistry([tapadIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['tapadId', 'tapad_id', 'cookie'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.tapadId'); + expect(bid.userId.tapadId).to.equal('test-tapad-id'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'tapad.com', + uids: [{id: 'test-tapad-id', atype: 1}] + }); + }); + }) + coreStorage.setCookie('tapad_id', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + it('test hook from liveIntentId html5', function (done) { // simulate existing browser local storage values localStorage.setItem('_li_pbid', JSON.stringify({'unifiedId': 'random-ls-identifier'})); @@ -1483,7 +1510,7 @@ describe('User ID', function () { coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('storage_criteo', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule, tapadIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], @@ -1495,7 +1522,8 @@ describe('User ID', function () { ['intentIqId', 'intentIqId', 'cookie'], ['zeotapIdPlus', 'IDP', 'cookie'], ['haloId', 'haloId', 'cookie'], - ['criteo', 'storage_criteo', 'cookie'])); + ['criteo', 'storage_criteo', 'cookie'], + ['tapadId', 'tapad_id', 'cookie'])); requestBidsHook(function () { adUnits.forEach(unit => { @@ -1588,6 +1616,7 @@ describe('User ID', function () { attachIdSystem(zeotapIdPlusSubmodule); attachIdSystem(haloIdSubmodule); attachIdSystem(criteoIdSubmodule); + attachIdSystem(tapadIdSubmodule); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], @@ -1599,7 +1628,8 @@ describe('User ID', function () { ['intentIqId', 'intentIqId', 'cookie'], ['zeotapIdPlus', 'IDP', 'cookie'], ['haloId', 'haloId', 'cookie'], - ['criteo', 'storage_criteo', 'cookie'])); + ['criteo', 'storage_criteo', 'cookie'], + ['tapadId', 'tapad_id', 'cookie'])); requestBidsHook(function () { adUnits.forEach(unit => { From 66fcb8c0ce83d404d5e913cfb05c73dcc5e4c6d9 Mon Sep 17 00:00:00 2001 From: supportGothamad <75782642+supportGothamad@users.noreply.github.com> Date: Mon, 18 Jan 2021 18:42:20 +0200 Subject: [PATCH 3/7] GothamAds prebid adapter (#6180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * init new pull request with GothamAds prebid adapter * new optional params (#6148) * Add the root domain check method as a util method added for use mod… (#6124) * Add the root domain check method as a global method added for use modules * Add a date suffix to keep the test cookie random * Per review, moved the findRootDomain method to utils out of the userId modules * More the domain method back the user module but attach it to the submodule so that userId modules can have access to it * Remove the storageManager as it is no longer used in utils * fix includes and find references in various files (#6183) * Fix typo in mediaforce bid adapter (#6181) * add mediaforce bid adapter * make use of unused variable language * Added native support for Mediaforce Bid Adapter * Fix test endpoint url for Mediaforce Bid Adapter * cleanup * Added the ability to send multiple bids in one ad request for mediaforce bid adapter * Fixes after review for mediaforce bid adapter * Fix typo in mediaforce bid adapter Co-authored-by: ksanksana * Prebid JS Native Phase 2 update: (#5411) * Prebid JS Native Phase 2 update: 1) Key Suppression for Native keys with sendTargetingKeys set to false at nativeParams.sendTargetingKeys or within asset i.e. nativeParams.image.sendTargetingKeys. Logic as follows: - If nativeParams.sendTargetingKeys set to false but asset has sendTargetingKeys set to true then add the KV - Else if nativeParams.sendTargetingKeys is set to true and asset does not have sendTargetingKeys defined or it is set to true then add KV - Else if nativeParams.sendTargetingKeys is not defined and asset does not have sendTargetingKeys defined or it is set to true then add KV If no conditions met then key is suppressed 2) Adds rendererUrl or adTemplate to the bid object for easy retrieval if assets requested from creative 3) Adds new logic to receiveMessage function in secureCreatives.js to answer data.action of 'allAssetRequest' with all available assets on the bid that have a value 4) Adds new native keys to constants.json * Update to simplify send asset logic * Fixed asset location typo * Update to include message listener for native resize and update adObject for auction to include creative height and trigger resize Co-authored-by: Michael Moschovas * Improve __uspapi behavior in iframe (#6072) * align locator + fix remove listener * update comment Co-authored-by: Reinout Stevens * Prebid 4.22.0 Release * Increment pre version * Sovrn: Add Support for Floor Module (#6155) * EX-2549 Pass segments parameter as imp.ext.dealids array * EX-2549 Address Jon's feedback * EX-2549 Reworked the solution * EX-2549 Blackbird compatibility * EX-2549 Address Jon's comments * EX-2549 Addressed upstream PR comments * added floor module support * added function check * rerun test Co-authored-by: Egor Gordeev Co-authored-by: Egor Gordeev <48566506+egsgordeev@users.noreply.github.com> * Lotame panorama id glv (#6185) * Add the root domain check method as a global method added for use modules * Add a date suffix to keep the test cookie random * Per review, moved the findRootDomain method to utils out of the userId modules * More the domain method back the user module but attach it to the submodule so that userId modules can have access to it * Remove the storageManager as it is no longer used in utils * Add the GVLID to the lotamePanoramaIdSystem * Use the GVL ID in the storage manager as well * Admixer bid adapter: add fpd (#6190) * Migrating to Prebid 1.0 * Migrating to Prebid 1.0 * Fix spec * add gdpr and usp * remove changes in gdpr_hello_world.html * Update gdpr_hello_world.html add spaces * add user syncs * remove comments * tests * add fpd to request Co-authored-by: atkachov * Add Adomain Targeting Key and Default Targeting Keys (#6116) * Add Adomain Targeting Key * add default targeting key Co-authored-by: Veronica Kim * Adkernel: torchad alias (#6191) * New feature: PBS anti alias (#6150) * PBS anti alias * lint * lint * update test * update test * Unit tests + dynamic aliasing * test bidder factory * Waardex Bid Adapter: Add video support, refactor code (#6149) * Add video support, refactor code * Add zoneId, refactor Co-authored-by: yegorheiz * init new pull request with GothamAds prebid adapter Co-authored-by: Drilon Kastrati Co-authored-by: Mark Co-authored-by: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Co-authored-by: Niksok Co-authored-by: ksanksana Co-authored-by: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Co-authored-by: Michael Moschovas Co-authored-by: Reinout Stevens Co-authored-by: Reinout Stevens Co-authored-by: Scott Menzer Co-authored-by: John Rosendahl Co-authored-by: Egor Gordeev Co-authored-by: Egor Gordeev <48566506+egsgordeev@users.noreply.github.com> Co-authored-by: Galphimbl Co-authored-by: atkachov Co-authored-by: Veronica Kim <43146383+vkimcm@users.noreply.github.com> Co-authored-by: Veronica Kim Co-authored-by: Denis Logachov Co-authored-by: Gena Co-authored-by: yegorWaardex <59033363+yegorWaardex@users.noreply.github.com> Co-authored-by: yegorheiz --- modules/gothamadsBidAdapter.js | 307 +++++++++++++++ modules/gothamadsBidAdapter.md | 104 +++++ test/spec/modules/gothamadsBidAdapter_spec.js | 369 ++++++++++++++++++ 3 files changed, 780 insertions(+) create mode 100644 modules/gothamadsBidAdapter.js create mode 100644 modules/gothamadsBidAdapter.md create mode 100644 test/spec/modules/gothamadsBidAdapter_spec.js diff --git a/modules/gothamadsBidAdapter.js b/modules/gothamadsBidAdapter.js new file mode 100644 index 00000000000..f2d2a9f5261 --- /dev/null +++ b/modules/gothamadsBidAdapter.js @@ -0,0 +1,307 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; +import {config} from '../src/config.js'; + +const BIDDER_CODE = 'gothamads'; +const ACCOUNTID_MACROS = '[account_id]'; +const URL_ENDPOINT = `https://us-e-node1.gothamads.com/bid?pass=${ACCOUNTID_MACROS}&integration=prebidjs`; +const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' }; +const NATIVE_PARAMS = { + title: { + id: 0, + name: 'title' + }, + icon: { + id: 2, + type: 1, + name: 'img' + }, + image: { + id: 3, + type: 3, + name: 'img' + }, + sponsoredBy: { + id: 5, + name: 'data', + type: 1 + }, + body: { + id: 4, + name: 'data', + type: 2 + }, + cta: { + id: 1, + type: 12, + name: 'data' + } +}; +const NATIVE_VERSION = '1.2'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: (bid) => { + return Boolean(bid.params.accountId) && Boolean(bid.params.placementId) + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: (validBidRequests, bidderRequest) => { + if (validBidRequests && validBidRequests.length === 0) return [] + let accuontId = validBidRequests[0].params.accountId; + const endpointURL = URL_ENDPOINT.replace(ACCOUNTID_MACROS, accuontId); + + let winTop = window; + let location; + try { + location = new URL(bidderRequest.refererInfo.referer) + winTop = window.top; + } catch (e) { + location = winTop.location; + utils.logMessage(e); + }; + + let bids = []; + for (let bidRequest of validBidRequests) { + let impObject = prepareImpObject(bidRequest); + let data = { + id: bidRequest.bidId, + test: config.getConfig('debug') ? 1 : 0, + cur: ['USD'], + device: { + w: winTop.screen.width, + h: winTop.screen.height, + language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '', + }, + site: { + page: location.pathname, + host: location.host + }, + source: { + tid: bidRequest.transactionId + }, + tmax: bidRequest.timeout, + imp: [impObject], + }; + bids.push(data) + } + return { + method: 'POST', + url: endpointURL, + data: bids + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: (serverResponse) => { + if (!serverResponse || !serverResponse.body) return [] + let GothamAdskResponse = serverResponse.body; + + let bids = []; + for (let response of GothamAdskResponse) { + let mediaType = response.seatbid[0].bid[0].ext && response.seatbid[0].bid[0].ext.mediaType ? response.seatbid[0].bid[0].ext.mediaType : BANNER; + + let bid = { + requestId: response.id, + cpm: response.seatbid[0].bid[0].price, + width: response.seatbid[0].bid[0].w, + height: response.seatbid[0].bid[0].h, + ttl: response.ttl || 1200, + currency: response.cur || 'USD', + netRevenue: true, + creativeId: response.seatbid[0].bid[0].crid, + dealId: response.seatbid[0].bid[0].dealid, + mediaType: mediaType + }; + + switch (mediaType) { + case VIDEO: + bid.vastXml = response.seatbid[0].bid[0].adm + bid.vastUrl = response.seatbid[0].bid[0].ext.vastUrl + break + case NATIVE: + bid.native = parseNative(response.seatbid[0].bid[0].adm) + break + default: + bid.ad = response.seatbid[0].bid[0].adm + } + + bids.push(bid); + } + + return bids; + }, +}; + +/** + * Determine type of request + * + * @param bidRequest + * @param type + * @returns {boolean} + */ +const checkRequestType = (bidRequest, type) => { + return (typeof utils.deepAccess(bidRequest, `mediaTypes.${type}`) !== 'undefined'); +} + +const parseNative = admObject => { + const { assets, link, imptrackers, jstracker } = admObject.native; + const result = { + clickUrl: link.url, + clickTrackers: link.clicktrackers || undefined, + impressionTrackers: imptrackers || undefined, + javascriptTrackers: jstracker ? [ jstracker ] : undefined + }; + assets.forEach(asset => { + const kind = NATIVE_ASSET_IDS[asset.id]; + const content = kind && asset[NATIVE_PARAMS[kind].name]; + if (content) { + result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; + } + }); + + return result; +} + +const prepareImpObject = (bidRequest) => { + let impObject = { + id: bidRequest.transactionId, + secure: 1, + ext: { + placementId: bidRequest.params.placementId + } + }; + if (checkRequestType(bidRequest, BANNER)) { + impObject.banner = addBannerParameters(bidRequest); + } + if (checkRequestType(bidRequest, VIDEO)) { + impObject.video = addVideoParameters(bidRequest); + } + if (checkRequestType(bidRequest, NATIVE)) { + impObject.native = { + ver: NATIVE_VERSION, + request: addNativeParameters(bidRequest) + }; + } + return impObject +}; + +const addNativeParameters = bidRequest => { + let impObject = { + id: bidRequest.transactionId, + ver: NATIVE_VERSION, + }; + + const assets = utils._map(bidRequest.mediaTypes.native, (bidParams, key) => { + const props = NATIVE_PARAMS[key]; + const asset = { + required: bidParams.required & 1, + }; + if (props) { + asset.id = props.id; + let wmin, hmin; + let aRatios = bidParams.aspect_ratios; + + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + wmin = aRatios.min_width || 0; + hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + } + + if (bidParams.sizes) { + const sizes = flatten(bidParams.sizes); + wmin = sizes[0]; + hmin = sizes[1]; + } + + asset[props.name] = {} + + if (bidParams.len) asset[props.name]['len'] = bidParams.len; + if (props.type) asset[props.name]['type'] = props.type; + if (wmin) asset[props.name]['wmin'] = wmin; + if (hmin) asset[props.name]['hmin'] = hmin; + + return asset; + } + }).filter(Boolean); + + impObject.assets = assets; + return impObject +} + +const addBannerParameters = (bidRequest) => { + let bannerObject = {}; + const size = parseSizes(bidRequest, 'banner'); + bannerObject.w = size[0]; + bannerObject.h = size[1]; + return bannerObject; +}; + +const parseSizes = (bid, mediaType) => { + let mediaTypes = bid.mediaTypes; + if (mediaType === 'video') { + let size = []; + if (mediaTypes.video && mediaTypes.video.w && mediaTypes.video.h) { + size = [ + mediaTypes.video.w, + mediaTypes.video.h + ]; + } else if (Array.isArray(utils.deepAccess(bid, 'mediaTypes.video.playerSize')) && bid.mediaTypes.video.playerSize.length === 1) { + size = bid.mediaTypes.video.playerSize[0]; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0 && Array.isArray(bid.sizes[0]) && bid.sizes[0].length > 1) { + size = bid.sizes[0]; + } + return size; + } + let sizes = []; + if (Array.isArray(mediaTypes.banner.sizes)) { + sizes = mediaTypes.banner.sizes[0]; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + sizes = bid.sizes + } else { + utils.logWarn('no sizes are setup or found'); + } + + return sizes +} + +const addVideoParameters = (bidRequest) => { + let videoObj = {}; + let supportParamsList = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'placement', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'] + + for (let param of supportParamsList) { + if (bidRequest.mediaTypes.video[param] !== undefined) { + videoObj[param] = bidRequest.mediaTypes.video[param]; + } + } + + const size = parseSizes(bidRequest, 'video'); + videoObj.w = size[0]; + videoObj.h = size[1]; + return videoObj; +} + +const flatten = arr => { + return [].concat(...arr); +} + +registerBidder(spec); diff --git a/modules/gothamadsBidAdapter.md b/modules/gothamadsBidAdapter.md new file mode 100644 index 00000000000..3105dff6c6c --- /dev/null +++ b/modules/gothamadsBidAdapter.md @@ -0,0 +1,104 @@ +# Overview + +``` +Module Name: GothamAds SSP Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@gothamads.com +``` + +# Description + +Module that connects to GothamAds SSP demand sources + +# Test Parameters +``` + var adUnits = [{ + code: 'placementId', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'gothamads', + params: { + placementId: 'hash', + accountId: 'accountId' + } + }] + }, + { + code: 'native_example', + // sizes: [[1, 1]], + mediaTypes: { + native: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + privacyLink: { + required: false + }, + body: { + required: true + }, + icon: { + required: true, + sizes: [50, 50] + } + } + + }, + bids: [ { + bidder: 'gothamads', + params: { + placementId: 'hash', + accountId: 'accountId' + } + }] + }, + { + code: 'video1', + sizes: [640,480], + mediaTypes: { video: { + minduration:0, + maxduration:999, + boxingallowed:1, + skip:0, + mimes:[ + 'application/javascript', + 'video/mp4' + ], + w:1920, + h:1080, + protocols:[ + 2 + ], + linearity:1, + api:[ + 1, + 2 + ] + } }, + bids: [ + { + bidder: 'gothamads', + params: { + placementId: 'hash', + accountId: 'accountId' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/gothamadsBidAdapter_spec.js b/test/spec/modules/gothamadsBidAdapter_spec.js new file mode 100644 index 00000000000..c638e7a0e3e --- /dev/null +++ b/test/spec/modules/gothamadsBidAdapter_spec.js @@ -0,0 +1,369 @@ +import { expect } from 'chai'; +import { spec } from 'modules/gothamadsBidAdapter.js'; + +const NATIVE_BID_REQUEST = { + code: 'native_example', + mediaTypes: { + native: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + privacyLink: { + required: false + }, + body: { + required: true + }, + icon: { + required: true, + sizes: [50, 50] + } + } + }, + bidder: 'gothamads', + params: { + placementId: 'hash', + accountId: 'accountId' + }, + timeout: 1000 + +}; + +const BANNER_BID_REQUEST = { + code: 'banner_example', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidder: 'gothamads', + params: { + placementId: 'hash', + accountId: 'accountId' + }, + timeout: 1000, + +} + +const bidRequest = { + refererInfo: { + referer: 'test.com' + } +} + +const VIDEO_BID_REQUEST = { + code: 'video1', + sizes: [640, 480], + mediaTypes: { video: { + minduration: 0, + maxduration: 999, + boxingallowed: 1, + skip: 0, + mimes: [ + 'application/javascript', + 'video/mp4' + ], + w: 1920, + h: 1080, + protocols: [ + 2 + ], + linearity: 1, + api: [ + 1, + 2 + ] + } + }, + + bidder: 'gothamads', + params: { + placementId: 'hash', + accountId: 'accountId' + }, + timeout: 1000 + +} + +const BANNER_BID_RESPONSE = { + id: 'request_id', + bidid: 'request_imp_id', + seatbid: [{ + bid: [{ + id: 'bid_id', + impid: 'request_imp_id', + price: 5, + adomain: ['example.com'], + adm: 'admcode', + crid: 'crid', + ext: { + mediaType: 'banner' + } + }], + }], +}; + +const VIDEO_BID_RESPONSE = { + id: 'request_id', + bidid: 'request_imp_id', + seatbid: [{ + bid: [{ + id: 'bid_id', + impid: 'request_imp_id', + price: 5, + adomain: ['example.com'], + adm: 'admcode', + crid: 'crid', + ext: { + mediaType: 'video', + vastUrl: 'http://example.vast', + } + }], + }], +}; + +let imgData = { + url: `https://example.com/image`, + w: 1200, + h: 627 +}; + +const NATIVE_BID_RESPONSE = { + id: 'request_id', + bidid: 'request_imp_id', + seatbid: [{ + bid: [{ + id: 'bid_id', + impid: 'request_imp_id', + price: 5, + adomain: ['example.com'], + adm: { native: + { + assets: [ + {id: 0, title: 'dummyText'}, + {id: 3, image: imgData}, + { + id: 5, + data: {value: 'organization.name'} + } + ], + link: {url: 'example.com'}, + imptrackers: ['tracker1.com', 'tracker2.com', 'tracker3.com'], + jstracker: 'tracker1.com' + } + }, + crid: 'crid', + ext: { + mediaType: 'native' + } + }], + }], +}; + +describe('GothamAdsAdapter', function() { + describe('isBidRequestValid', function() { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(NATIVE_BID_REQUEST)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, NATIVE_BID_REQUEST); + delete bid.params; + bid.params = { + 'IncorrectParam': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('build Native Request', function () { + const request = spec.buildRequests([NATIVE_BID_REQUEST], bidRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(request).to.exist; + expect(request.method).to.exist; + expect(request.url).to.exist; + expect(request.data).to.exist; + }); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(request.url).to.equal('https://us-e-node1.gothamads.com/bid?pass=accountId&integration=prebidjs'); + }); + + it('Returns empty data if no valid requests are passed', function () { + let serverRequest = spec.buildRequests([]); + expect(serverRequest).to.be.an('array').that.is.empty; + }); + }); + + describe('build Banner Request', function () { + const request = spec.buildRequests([BANNER_BID_REQUEST]); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(request).to.exist; + expect(request.method).to.exist; + expect(request.url).to.exist; + expect(request.data).to.exist; + }); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(request.url).to.equal('https://us-e-node1.gothamads.com/bid?pass=accountId&integration=prebidjs'); + }); + }); + + describe('build Video Request', function () { + const request = spec.buildRequests([VIDEO_BID_REQUEST]); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(request).to.exist; + expect(request.method).to.exist; + expect(request.url).to.exist; + expect(request.data).to.exist; + }); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(request.url).to.equal('https://us-e-node1.gothamads.com/bid?pass=accountId&integration=prebidjs'); + }); + }); + + describe('interpretResponse', function () { + it('Empty response must return empty array', function() { + const emptyResponse = null; + let response = spec.interpretResponse(emptyResponse); + + expect(response).to.be.an('array').that.is.empty; + }) + + it('Should interpret banner response', function () { + const bannerResponse = { + body: [BANNER_BID_RESPONSE] + } + + const expectedBidResponse = { + requestId: BANNER_BID_RESPONSE.id, + cpm: BANNER_BID_RESPONSE.seatbid[0].bid[0].price, + width: BANNER_BID_RESPONSE.seatbid[0].bid[0].w, + height: BANNER_BID_RESPONSE.seatbid[0].bid[0].h, + ttl: BANNER_BID_RESPONSE.ttl || 1200, + currency: BANNER_BID_RESPONSE.cur || 'USD', + netRevenue: true, + creativeId: BANNER_BID_RESPONSE.seatbid[0].bid[0].crid, + dealId: BANNER_BID_RESPONSE.seatbid[0].bid[0].dealid, + mediaType: 'banner', + ad: BANNER_BID_RESPONSE.seatbid[0].bid[0].adm + } + + let bannerResponses = spec.interpretResponse(bannerResponse); + + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); + expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); + expect(dataItem.ad).to.equal(expectedBidResponse.ad); + expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); + expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(expectedBidResponse.currency); + expect(dataItem.width).to.equal(expectedBidResponse.width); + expect(dataItem.height).to.equal(expectedBidResponse.height); + }); + + it('Should interpret video response', function () { + const videoResponse = { + body: [VIDEO_BID_RESPONSE] + } + + const expectedBidResponse = { + requestId: VIDEO_BID_RESPONSE.id, + cpm: VIDEO_BID_RESPONSE.seatbid[0].bid[0].price, + width: VIDEO_BID_RESPONSE.seatbid[0].bid[0].w, + height: VIDEO_BID_RESPONSE.seatbid[0].bid[0].h, + ttl: VIDEO_BID_RESPONSE.ttl || 1200, + currency: VIDEO_BID_RESPONSE.cur || 'USD', + netRevenue: true, + creativeId: VIDEO_BID_RESPONSE.seatbid[0].bid[0].crid, + dealId: VIDEO_BID_RESPONSE.seatbid[0].bid[0].dealid, + mediaType: 'video', + vastXml: VIDEO_BID_RESPONSE.seatbid[0].bid[0].adm, + vastUrl: VIDEO_BID_RESPONSE.seatbid[0].bid[0].ext.vastUrl + } + + let videoResponses = spec.interpretResponse(videoResponse); + + expect(videoResponses).to.be.an('array').that.is.not.empty; + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastXml', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); + expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); + expect(dataItem.vastXml).to.equal(expectedBidResponse.vastXml) + expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); + expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(expectedBidResponse.currency); + expect(dataItem.width).to.equal(expectedBidResponse.width); + expect(dataItem.height).to.equal(expectedBidResponse.height); + }); + + it('Should interpret native response', function () { + const nativeResponse = { + body: [NATIVE_BID_RESPONSE] + } + + const expectedBidResponse = { + requestId: NATIVE_BID_RESPONSE.id, + cpm: NATIVE_BID_RESPONSE.seatbid[0].bid[0].price, + width: NATIVE_BID_RESPONSE.seatbid[0].bid[0].w, + height: NATIVE_BID_RESPONSE.seatbid[0].bid[0].h, + ttl: NATIVE_BID_RESPONSE.ttl || 1200, + currency: NATIVE_BID_RESPONSE.cur || 'USD', + netRevenue: true, + creativeId: NATIVE_BID_RESPONSE.seatbid[0].bid[0].crid, + dealId: NATIVE_BID_RESPONSE.seatbid[0].bid[0].dealid, + mediaType: 'native', + native: {clickUrl: NATIVE_BID_RESPONSE.seatbid[0].bid[0].adm.native.link.url} + } + + let nativeResponses = spec.interpretResponse(nativeResponse); + + expect(nativeResponses).to.be.an('array').that.is.not.empty; + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'native', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); + expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); + expect(dataItem.native.clickUrl).to.equal(expectedBidResponse.native.clickUrl) + expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); + expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(expectedBidResponse.currency); + expect(dataItem.width).to.equal(expectedBidResponse.width); + expect(dataItem.height).to.equal(expectedBidResponse.height); + }); + }); +}) From 1729dd34b22f02ab86c2df7ff0d2a6ecd89b83e9 Mon Sep 17 00:00:00 2001 From: Hai Lee Date: Tue, 19 Jan 2021 00:04:50 +0700 Subject: [PATCH 4/7] add AdTrue bid adapter (#6166) * add AdTrue bid adapter * add AdTrue Bid Adapter unit tests * add AdTrue Bid Adapter unit tests * add AdTrue bid adapter * add AdTrue Bid Adapter unit tests * add AdTrue Bid Adapter unit tests * add AdTrue bid adapter --- modules/adtrueBidAdapter.js | 637 +++++++++++++++++++++ modules/adtrueBidAdapter.md | 36 ++ test/spec/modules/adtrueBidAdapter_spec.js | 370 ++++++++++++ 3 files changed, 1043 insertions(+) create mode 100644 modules/adtrueBidAdapter.js create mode 100644 modules/adtrueBidAdapter.md create mode 100644 test/spec/modules/adtrueBidAdapter_spec.js diff --git a/modules/adtrueBidAdapter.js b/modules/adtrueBidAdapter.js new file mode 100644 index 00000000000..fa83f9f29f7 --- /dev/null +++ b/modules/adtrueBidAdapter.js @@ -0,0 +1,637 @@ +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {getStorageManager} from '../src/storageManager.js'; + +const storage = getStorageManager(); +const BIDDER_CODE = 'adtrue'; +const ADTRUE_CURRENCY = 'USD'; +const ENDPOINT_URL = 'https://hb.adtrue.com/prebid/auction'; +const LOG_WARN_PREFIX = 'AdTrue: '; +const AUCTION_TYPE = 1; +const UNDEFINED = undefined; +const DEFAULT_WIDTH = 0; +const DEFAULT_HEIGHT = 0; +const NET_REVENUE = false; +const USER_SYNC_URL_IFRAME = 'https://hb.adtrue.com/prebid/usersync?t=iframe&p='; +const USER_SYNC_URL_IMAGE = 'https://hb.adtrue.com/prebid/usersync?t=img&p='; +let publisherId = 0; +let NATIVE_ASSET_ID_TO_KEY_MAP = {}; +const DATA_TYPES = { + 'NUMBER': 'number', + 'STRING': 'string', + 'BOOLEAN': 'boolean', + 'ARRAY': 'array', + 'OBJECT': 'object' +}; +const VIDEO_CUSTOM_PARAMS = { + 'mimes': DATA_TYPES.ARRAY, + 'minduration': DATA_TYPES.NUMBER, + 'maxduration': DATA_TYPES.NUMBER, + 'startdelay': DATA_TYPES.NUMBER, + 'playbackmethod': DATA_TYPES.ARRAY, + 'api': DATA_TYPES.ARRAY, + 'protocols': DATA_TYPES.ARRAY, + 'w': DATA_TYPES.NUMBER, + 'h': DATA_TYPES.NUMBER, + 'battr': DATA_TYPES.ARRAY, + 'linearity': DATA_TYPES.NUMBER, + 'placement': DATA_TYPES.NUMBER, + 'minbitrate': DATA_TYPES.NUMBER, + 'maxbitrate': DATA_TYPES.NUMBER +} + +const NATIVE_ASSETS = { + 'TITLE': {ID: 1, KEY: 'title', TYPE: 0}, + 'IMAGE': {ID: 2, KEY: 'image', TYPE: 0}, + 'ICON': {ID: 3, KEY: 'icon', TYPE: 0}, + 'SPONSOREDBY': {ID: 4, KEY: 'sponsoredBy', TYPE: 1}, // please note that type of SPONSORED is also 1 + 'BODY': {ID: 5, KEY: 'body', TYPE: 2}, // please note that type of DESC is also set to 2 + 'CLICKURL': {ID: 6, KEY: 'clickUrl', TYPE: 0}, + 'VIDEO': {ID: 7, KEY: 'video', TYPE: 0}, + 'EXT': {ID: 8, KEY: 'ext', TYPE: 0}, + 'DATA': {ID: 9, KEY: 'data', TYPE: 0}, + 'LOGO': {ID: 10, KEY: 'logo', TYPE: 0}, + 'SPONSORED': {ID: 11, KEY: 'sponsored', TYPE: 1}, // please note that type of SPONSOREDBY is also set to 1 + 'DESC': {ID: 12, KEY: 'data', TYPE: 2}, // please note that type of BODY is also set to 2 + 'RATING': {ID: 13, KEY: 'rating', TYPE: 3}, + 'LIKES': {ID: 14, KEY: 'likes', TYPE: 4}, + 'DOWNLOADS': {ID: 15, KEY: 'downloads', TYPE: 5}, + 'PRICE': {ID: 16, KEY: 'price', TYPE: 6}, + 'SALEPRICE': {ID: 17, KEY: 'saleprice', TYPE: 7}, + 'PHONE': {ID: 18, KEY: 'phone', TYPE: 8}, + 'ADDRESS': {ID: 19, KEY: 'address', TYPE: 9}, + 'DESC2': {ID: 20, KEY: 'desc2', TYPE: 10}, + 'DISPLAYURL': {ID: 21, KEY: 'displayurl', TYPE: 11}, + 'CTA': {ID: 22, KEY: 'cta', TYPE: 12} +}; + +function _getDomainFromURL(url) { + let anchor = document.createElement('a'); + anchor.href = url; + return anchor.hostname; +} + +function _parseSlotParam(paramName, paramValue) { + switch (paramName) { + case 'reserve': + return parseFloat(paramValue) || UNDEFINED; + default: + return paramValue; + } +} + +let platform = (function getPlatform() { + var ua = navigator.userAgent; + if (ua.indexOf('Android') > -1 || ua.indexOf('Adr') > -1) { + return 'Android' + } + if (ua.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)) { + return 'iOS' + } + return 'windows' +})(); + +function _generateGUID() { + var d = new Date().getTime(); + var guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = (d + Math.random() * 16) % 16 | 0; + d = Math.floor(d / 16); + return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16); + }) + return guid; +} + +function _isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +function _isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +function _parseAdSlot(bid) { + bid.params.width = 0; + bid.params.height = 0; + // check if size is mentioned in sizes array. in that case do not check for @ in adslot + if (bid.hasOwnProperty('mediaTypes') && + bid.mediaTypes.hasOwnProperty(BANNER) && + bid.mediaTypes.banner.hasOwnProperty('sizes')) { + var i = 0; + var sizeArray = []; + for (; i < bid.mediaTypes.banner.sizes.length; i++) { + if (bid.mediaTypes.banner.sizes[i].length === 2) { // sizes[i].length will not be 2 in case where size is set as fluid, we want to skip that entry + sizeArray.push(bid.mediaTypes.banner.sizes[i]); + } + } + bid.mediaTypes.banner.sizes = sizeArray; + if (bid.mediaTypes.banner.sizes.length >= 1) { + // set the first size in sizes array in bid.params.width and bid.params.height. These will be sent as primary size. + // The rest of the sizes will be sent in format array. + bid.params.width = bid.mediaTypes.banner.sizes[0][0]; + bid.params.height = bid.mediaTypes.banner.sizes[0][1]; + bid.mediaTypes.banner.sizes = bid.mediaTypes.banner.sizes.splice(1, bid.mediaTypes.banner.sizes.length - 1); + } + } +} + +function _initConf(refererInfo) { + return { + pageURL: (refererInfo && refererInfo.referer) ? refererInfo.referer : window.location.href, + refURL: window.document.referrer + }; +} + +function _getLanguage() { + const language = navigator.language ? 'language' : 'userLanguage'; + return navigator[language].split('-')[0]; +} + +function _createOrtbTemplate(conf) { + var guid; + if (storage.getDataFromLocalStorage('adtrue_user_id') == null) { + storage.setDataInLocalStorage('adtrue_user_id', _generateGUID()) + } + guid = storage.getDataFromLocalStorage('adtrue_user_id') + + return { + id: '' + new Date().getTime(), + at: AUCTION_TYPE, + cur: [ADTRUE_CURRENCY], + imp: [], + site: { + page: conf.pageURL, + ref: conf.refURL, + publisher: {} + }, + device: { + ip: '', + ua: navigator.userAgent, + os: platform, + js: 1, + dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, + h: screen.height, + w: screen.width, + language: _getLanguage(), + devicetype: _isMobile() ? 1 : _isConnectedTV() ? 3 : 2, + geo: { + country: '{country_code}', + type: 0, + ipservice: 1, + region: '', + city: '', + }, + }, + user: { + id: guid + }, + ext: {} + }; +} + +function _checkParamDataType(key, value, datatype) { + var errMsg = 'Ignoring param key: ' + key + ', expects ' + datatype + ', found ' + typeof value; + var functionToExecute; + switch (datatype) { + case DATA_TYPES.BOOLEAN: + functionToExecute = utils.isBoolean; + break; + case DATA_TYPES.NUMBER: + functionToExecute = utils.isNumber; + break; + case DATA_TYPES.STRING: + functionToExecute = utils.isStr; + break; + case DATA_TYPES.ARRAY: + functionToExecute = utils.isArray; + break; + } + if (functionToExecute(value)) { + return value; + } + utils.logWarn(LOG_WARN_PREFIX + errMsg); + return UNDEFINED; +} + +function _parseNativeResponse(bid, newBid) { + newBid.native = {}; + if (bid.hasOwnProperty('adm')) { + var adm = ''; + try { + adm = JSON.parse(bid.adm.replace(/\\/g, '')); + } catch (ex) { + // utils.logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native reponse for ad response: ' + newBid.adm); + return; + } + if (adm && adm.native && adm.native.assets && adm.native.assets.length > 0) { + newBid.mediaType = NATIVE; + for (let i = 0, len = adm.native.assets.length; i < len; i++) { + switch (adm.native.assets[i].id) { + case NATIVE_ASSETS.TITLE.ID: + newBid.native.title = adm.native.assets[i].title && adm.native.assets[i].title.text; + break; + case NATIVE_ASSETS.IMAGE.ID: + newBid.native.image = { + url: adm.native.assets[i].img && adm.native.assets[i].img.url, + height: adm.native.assets[i].img && adm.native.assets[i].img.h, + width: adm.native.assets[i].img && adm.native.assets[i].img.w, + }; + break; + case NATIVE_ASSETS.ICON.ID: + newBid.native.icon = { + url: adm.native.assets[i].img && adm.native.assets[i].img.url, + height: adm.native.assets[i].img && adm.native.assets[i].img.h, + width: adm.native.assets[i].img && adm.native.assets[i].img.w, + }; + break; + case NATIVE_ASSETS.SPONSOREDBY.ID: + case NATIVE_ASSETS.BODY.ID: + case NATIVE_ASSETS.LIKES.ID: + case NATIVE_ASSETS.DOWNLOADS.ID: + case NATIVE_ASSETS.PRICE: + case NATIVE_ASSETS.SALEPRICE.ID: + case NATIVE_ASSETS.PHONE.ID: + case NATIVE_ASSETS.ADDRESS.ID: + case NATIVE_ASSETS.DESC2.ID: + case NATIVE_ASSETS.CTA.ID: + case NATIVE_ASSETS.RATING.ID: + case NATIVE_ASSETS.DISPLAYURL.ID: + newBid.native[NATIVE_ASSET_ID_TO_KEY_MAP[adm.native.assets[i].id]] = adm.native.assets[i].data && adm.native.assets[i].data.value; + break; + } + } + newBid.native.clickUrl = adm.native.link && adm.native.link.url; + newBid.native.clickTrackers = (adm.native.link && adm.native.link.clicktrackers) || []; + newBid.native.impressionTrackers = adm.native.imptrackers || []; + newBid.native.jstracker = adm.native.jstracker || []; + if (!newBid.width) { + newBid.width = DEFAULT_WIDTH; + } + if (!newBid.height) { + newBid.height = DEFAULT_HEIGHT; + } + } + } +} + +function _createBannerRequest(bid) { + var sizes = bid.mediaTypes.banner.sizes; + var format = []; + var bannerObj; + if (sizes !== UNDEFINED && utils.isArray(sizes)) { + bannerObj = {}; + if (!bid.params.width && !bid.params.height) { + if (sizes.length === 0) { + // i.e. since bid.params does not have width or height, and length of sizes is 0, need to ignore this banner imp + bannerObj = UNDEFINED; + utils.logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); + return bannerObj; + } else { + bannerObj.w = parseInt(sizes[0][0], 10); + bannerObj.h = parseInt(sizes[0][1], 10); + sizes = sizes.splice(1, sizes.length - 1); + } + } else { + bannerObj.w = bid.params.width; + bannerObj.h = bid.params.height; + } + if (sizes.length > 0) { + format = []; + sizes.forEach(function (size) { + if (size.length > 1) { + format.push({w: size[0], h: size[1]}); + } + }); + if (format.length > 0) { + bannerObj.format = format; + } + } + bannerObj.pos = 0; + bannerObj.topframe = utils.inIframe() ? 0 : 1; + } else { + utils.logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); + bannerObj = UNDEFINED; + } + return bannerObj; +} + +function _createVideoRequest(bid) { + var videoData = bid.params.video; + var videoObj; + + if (videoData !== UNDEFINED) { + videoObj = {}; + for (var key in VIDEO_CUSTOM_PARAMS) { + if (videoData.hasOwnProperty(key)) { + videoObj[key] = _checkParamDataType(key, videoData[key], VIDEO_CUSTOM_PARAMS[key]); + } + } + // read playersize and assign to h and w. + if (utils.isArray(bid.mediaTypes.video.playerSize[0])) { + videoObj.w = parseInt(bid.mediaTypes.video.playerSize[0][0], 10); + videoObj.h = parseInt(bid.mediaTypes.video.playerSize[0][1], 10); + } else if (utils.isNumber(bid.mediaTypes.video.playerSize[0])) { + videoObj.w = parseInt(bid.mediaTypes.video.playerSize[0], 10); + videoObj.h = parseInt(bid.mediaTypes.video.playerSize[1], 10); + } + if (bid.params.video.hasOwnProperty('skippable')) { + videoObj.ext = { + 'video_skippable': bid.params.video.skippable ? 1 : 0 + }; + } + } else { + videoObj = UNDEFINED; + utils.logWarn(LOG_WARN_PREFIX + 'Error: Video config params missing for adunit: ' + bid.params.adUnit + ' with mediaType set as video. Ignoring video impression in the adunit.'); + } + return videoObj; +} + +function _checkMediaType(adm, newBid) { + var admStr = ''; + var videoRegex = new RegExp(/VAST\s+version/); + newBid.mediaType = BANNER; + if (videoRegex.test(adm)) { + newBid.mediaType = VIDEO; + } else { + try { + admStr = JSON.parse(adm.replace(/\\/g, '')); + if (admStr && admStr.native) { + newBid.mediaType = NATIVE; + } + } catch (e) { + utils.logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native reponse for ad response: ' + adm); + } + } +} + +function _createImpressionObject(bid, conf) { + var impObj = {}; + var bannerObj; + var videoObj; + var sizes = bid.hasOwnProperty('sizes') ? bid.sizes : []; + var mediaTypes = ''; + var format = []; + + impObj = { + id: bid.bidId, + tagid: String(bid.params.zoneId || undefined), + bidfloor: _parseSlotParam('reserve', bid.params.reserve), + secure: 1, + ext: {}, + bidfloorcur: ADTRUE_CURRENCY + }; + + if (bid.hasOwnProperty('mediaTypes')) { + for (mediaTypes in bid.mediaTypes) { + switch (mediaTypes) { + case BANNER: + bannerObj = _createBannerRequest(bid); + if (bannerObj !== UNDEFINED) { + impObj.banner = bannerObj; + } + break; + case VIDEO: + videoObj = _createVideoRequest(bid); + if (videoObj !== UNDEFINED) { + impObj.video = videoObj; + } + break; + } + } + } else { + // mediaTypes is not present, so this is a banner only impression + // this part of code is required for older testcases with no 'mediaTypes' to run succesfully. + bannerObj = { + pos: 0, + w: bid.params.width, + h: bid.params.height, + topframe: utils.inIframe() ? 0 : 1 + }; + if (utils.isArray(sizes) && sizes.length > 1) { + sizes = sizes.splice(1, sizes.length - 1); + sizes.forEach(size => { + format.push({ + w: size[0], + h: size[1] + }); + }); + bannerObj.format = format; + } + impObj.banner = bannerObj; + } + + return impObj.hasOwnProperty(BANNER) || + impObj.hasOwnProperty(NATIVE) || + impObj.hasOwnProperty(VIDEO) ? impObj : UNDEFINED; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['banner', 'video'], + + isBidRequestValid: function (bid) { + if (bid && bid.params) { + if (!bid.params.zoneId) { + utils.logWarn(LOG_WARN_PREFIX + 'Error: missing zoneId'); + return false; + } + if (!bid.params.publisherId) { + utils.logWarn(LOG_WARN_PREFIX + 'Error: missing publisherId'); + return false; + } + return true; + } + return false; + }, + + buildRequests: function (validBidRequests, bidderRequest) { + let refererInfo; + if (bidderRequest && bidderRequest.refererInfo) { + refererInfo = bidderRequest.refererInfo; + } + let conf = _initConf(refererInfo); + let payload = _createOrtbTemplate(conf); + let bidCurrency = ''; + let bid; + validBidRequests.forEach(originalBid => { + bid = utils.deepClone(originalBid); + _parseAdSlot(bid); + + conf.zoneId = conf.zoneId || bid.params.zoneId; + conf.pubId = conf.pubId || bid.params.publisherId; + + conf.transactionId = bid.transactionId; + if (bidCurrency === '') { + bidCurrency = bid.params.currency || UNDEFINED; + } else if (bid.params.hasOwnProperty('currency') && bidCurrency !== bid.params.currency) { + utils.logWarn(LOG_WARN_PREFIX + 'Currency specifier ignored. Only one currency permitted.'); + } + bid.params.currency = bidCurrency; + + var impObj = _createImpressionObject(bid, conf); + if (impObj) { + payload.imp.push(impObj); + } + }); + if (payload.imp.length == 0) { + return; + } + publisherId = conf.pubId.trim(); + + payload.site.publisher.id = conf.pubId.trim(); + payload.ext.wrapper = {}; + + payload.ext.wrapper.transactionId = conf.transactionId; + payload.ext.wrapper.wiid = conf.wiid || bidderRequest.auctionId; + payload.ext.wrapper.wp = 'pbjs'; + + payload.user.geo = {}; + payload.device.geo = payload.user.geo; + payload.site.page = conf.pageURL; + payload.site.domain = _getDomainFromURL(payload.site.page); + + if (typeof config.getConfig('content') === 'object') { + payload.site.content = config.getConfig('content'); + } + + if (typeof config.getConfig('device') === 'object') { + payload.device = Object.assign(payload.device, config.getConfig('device')); + } + + utils.deepSetValue(payload, 'source.tid', conf.transactionId); + + // test bids + if (window.location.href.indexOf('adtrueTest=true') !== -1) { + payload.test = 1; + } + // adding schain object + if (validBidRequests[0].schain) { + utils.deepSetValue(payload, 'source.ext.schain', validBidRequests[0].schain); + } + + // Attaching GDPR Consent Params + if (bidderRequest && bidderRequest.gdprConsent) { + utils.deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + utils.deepSetValue(payload, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + } + + // CCPA + if (bidderRequest && bidderRequest.uspConsent) { + utils.deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + // coppa compliance + if (config.getConfig('coppa') === true) { + utils.deepSetValue(payload, 'regs.coppa', 1); + } + + return { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(payload), + bidderRequests: bidderRequest + }; + }, + interpretResponse: function (serverResponses, bidderRequest) { + const bidResponses = []; + var respCur = ADTRUE_CURRENCY; + let parsedRequest = JSON.parse(bidderRequest.data); + let parsedReferrer = parsedRequest.site && parsedRequest.site.ref ? parsedRequest.site.ref : ''; + try { + if (serverResponses.body && serverResponses.body.seatbid && utils.isArray(serverResponses.body.seatbid)) { + // Supporting multiple bid responses for same adSize + respCur = serverResponses.body.cur || respCur; + serverResponses.body.seatbid.forEach(seatbidder => { + seatbidder.bid && + utils.isArray(seatbidder.bid) && + seatbidder.bid.forEach(bid => { + let newBid = { + requestId: bid.impid, + cpm: (parseFloat(bid.price) || 0).toFixed(2), + width: bid.w, + height: bid.h, + creativeId: bid.crid || bid.id, + dealId: bid.dealid, + currency: respCur, + netRevenue: NET_REVENUE, + ttl: 300, + referrer: parsedReferrer, + ad: bid.adm, + partnerImpId: bid.id || '' // partner impression Id + }; + if (parsedRequest.imp && parsedRequest.imp.length > 0) { + parsedRequest.imp.forEach(req => { + if (bid.impid === req.id) { + _checkMediaType(bid.adm, newBid); + switch (newBid.mediaType) { + case BANNER: + break; + case VIDEO: + break; + case NATIVE: + _parseNativeResponse(bid, newBid); + break; + } + } + }); + } + + newBid.meta = {}; + if (bid.ext && bid.ext.dspid) { + newBid.meta.networkId = bid.ext.dspid; + } + if (bid.ext && bid.ext.advid) { + newBid.meta.buyerId = bid.ext.advid; + } + if (bid.adomain && bid.adomain.length > 0) { + newBid.meta.advertiserDomains = bid.adomain; + newBid.meta.clickUrl = bid.adomain[0]; + } + + // adserverTargeting + if (seatbidder.ext && seatbidder.ext.buyid) { + newBid.adserverTargeting = { + 'hb_buyid_adtrue': seatbidder.ext.buyid + }; + } + + bidResponses.push(newBid); + }); + }); + } + } catch (error) { + utils.logError(error); + } + return bidResponses; + }, + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { + let syncurl = '' + publisherId; + + if (gdprConsent) { + syncurl += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); + syncurl += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); + } + if (uspConsent) { + syncurl += '&us_privacy=' + encodeURIComponent(uspConsent); + } + + // coppa compliance + if (config.getConfig('coppa') === true) { + syncurl += '&coppa=1'; + } + + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: USER_SYNC_URL_IFRAME + syncurl + }]; + } else { + return [{ + type: 'image', + url: USER_SYNC_URL_IMAGE + syncurl + }]; + } + } +}; +registerBidder(spec); diff --git a/modules/adtrueBidAdapter.md b/modules/adtrueBidAdapter.md new file mode 100644 index 00000000000..6d0c6f2c125 --- /dev/null +++ b/modules/adtrueBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: AdTrue Bid Adapter +Module Type: Bidder Adapter +Maintainer: ssp@adtrue.com +``` + +# Description + +Connects to AdTrue exchange for bids. +AdTrue bid adapter supports Banner currently. + +# Test Parameters +``` +var adUnits = [ + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'adtrue', + params: { + zoneId: '21423', // required, Zone Id provided by AdTrue + publisherId: '1491', // required, Publisher Id provided by AdTrue + reserve: 0.1 // optional + } + } + ] + } +]; +``` diff --git a/test/spec/modules/adtrueBidAdapter_spec.js b/test/spec/modules/adtrueBidAdapter_spec.js new file mode 100644 index 00000000000..a7732d6aeb7 --- /dev/null +++ b/test/spec/modules/adtrueBidAdapter_spec.js @@ -0,0 +1,370 @@ +import {expect} from 'chai' +import {spec} from 'modules/adtrueBidAdapter.js' +import {newBidder} from 'src/adapters/bidderFactory.js' +import * as utils from '../../../src/utils.js'; +import {config} from 'src/config.js'; + +describe('AdTrueBidAdapter', function () { + const adapter = newBidder(spec) + let bidRequests; + let bidResponses; + beforeEach(function () { + bidRequests = [ + { + bidder: 'adtrue', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + publisherId: '1212', + zoneId: '21423', + reserve: 0.2 + }, + placementCode: 'adunit-code-1', + sizes: [[300, 250]], + bidId: '23acc48ad47af5', + requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + schain: { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, + + { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 2 + } + ] + } + } + ]; + bidResponses = { + 'body': { + 'id': '1610681506302', + 'seatbid': [ + { + 'bid': [ + { + 'id': '1', + 'impid': '201fb513ca24e9', + 'price': 2.880000114440918, + 'burl': 'https://hb.adtrue.com/prebid/win-notify?impid=1610681506302&wp=${AUCTION_PRICE}', + 'adm': '', + 'adid': '1610681506302', + 'adomain': [ + 'adtrue.com' + ], + 'cid': 'f6l0r6n', + 'crid': 'abc77au4', + 'attr': [], + 'w': 300, + 'h': 250 + } + ], + 'seat': 'adtrue', + 'group': 0 + } + ], + 'bidid': '1610681506302', + 'cur': 'USD' + } + }; + }); + + describe('.code', function () { + it('should return a bidder code of adtrue', function () { + expect(spec.code).to.equal('adtrue') + }) + }) + + describe('inherited functions', function () { + it('should exist and be a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }) + describe('implementation', function () { + describe('Bid validations', function () { + it('valid bid case', function () { + let validBid = { + bidder: 'adtrue', + params: { + zoneId: '21423', + publisherId: '1212' + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + it('invalid bid case: publisherId not passed', function () { + let validBid = { + bidder: 'adtrue', + params: { + zoneId: '21423' + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('valid bid case: zoneId is not passed', function () { + let validBid = { + bidder: 'adtrue', + params: { + publisherId: '1212' + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('should return false if there are no params', () => { + const bid = { + 'bidder': 'adtrue', + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + banner: { + sizes: [[300, 250]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false if there is no publisherId param', () => { + const bid = { + 'bidder': 'adtrue', + 'adUnitCode': 'adunit-code', + params: { + zoneId: '21423', + }, + 'mediaTypes': { + banner: { + sizes: [[300, 250]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false if there is no zoneId param', () => { + const bid = { + 'bidder': 'adtrue', + 'adUnitCode': 'adunit-code', + params: { + publisherId: '1212', + }, + 'mediaTypes': { + banner: { + sizes: [[300, 250]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return true if the bid is valid', () => { + const bid = { + 'bidder': 'adtrue', + 'adUnitCode': 'adunit-code', + params: { + zoneId: '21423', + publisherId: '1212', + }, + 'mediaTypes': { + banner: { + sizes: [[300, 250]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + describe('Request formation', function () { + it('buildRequests function should not modify original bidRequests object', function () { + let originalBidRequests = utils.deepClone(bidRequests); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + expect(bidRequests).to.deep.equal(originalBidRequests); + }); + + it('Endpoint/method checking', function () { + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + expect(request.url).to.equal('https://hb.adtrue.com/prebid/auction'); + expect(request.method).to.equal('POST'); + }); + + it('test flag not sent when adtrueTest=true is absent in page url', function () { + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + expect(data.test).to.equal(undefined); + }); + it('Request params check', function () { + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + expect(data.at).to.equal(1); // auction type + expect(data.cur[0]).to.equal('USD'); // currency + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.source.tid).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id + expect(data.imp[0].bidfloor).to.equal(bidRequests[0].params.reserve); // reverse + expect(data.imp[0].tagid).to.equal(bidRequests[0].params.zoneId); // zoneId + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.source.ext.schain).to.deep.equal(bidRequests[0].schain); + }); + it('Request params check with GDPR Consent', function () { + let bidRequest = { + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true + } + }; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + expect(data.user.ext.consent).to.equal('kjfdniwjnifwenrif3'); + expect(data.at).to.equal(1); // auction type + expect(data.cur[0]).to.equal('USD'); // currency + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.source.tid).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id + expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.reserve)); // reverse + expect(data.imp[0].tagid).to.equal(bidRequests[0].params.zoneId); // zoneId + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.source.ext.schain).to.deep.equal(bidRequests[0].schain); + }); + it('Request params check with USP/CCPA Consent', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + expect(data.regs.ext.us_privacy).to.equal('1NYN');// USP/CCPAs + expect(data.at).to.equal(1); // auction type + expect(data.cur[0]).to.equal('USD'); // currency + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.source.tid).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id + expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.reserve)); // reverse + expect(data.imp[0].tagid).to.equal(bidRequests[0].params.zoneId); // zoneId + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.source.ext.schain).to.deep.equal(bidRequests[0].schain); + }); + + it('should NOT include coppa flag in bid request if coppa config is not present', () => { + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + if (data.regs) { + // in case GDPR is set then data.regs will exist + expect(data.regs.coppa).to.equal(undefined); + } else { + expect(data.regs).to.equal(undefined); + } + }); + it('should include coppa flag in bid request if coppa is set to true', () => { + let sandbox = sinon.sandbox.create(); + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'coppa': true + }; + return config[key]; + }); + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.regs.coppa).to.equal(1); + sandbox.restore(); + }); + it('should NOT include coppa flag in bid request if coppa is set to false', () => { + let sandbox = sinon.sandbox.create(); + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'coppa': false + }; + return config[key]; + }); + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + if (data.regs) { + // in case GDPR is set then data.regs will exist + expect(data.regs.coppa).to.equal(undefined); + } else { + expect(data.regs).to.equal(undefined); + } + sandbox.restore(); + }); + }); + }); + describe('Response checking', function () { + it('should check for valid response values', function () { + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + let response = spec.interpretResponse(bidResponses, request); + expect(response).to.be.an('array').with.length.above(0); + expect(response[0].requestId).to.equal(bidResponses.body.seatbid[0].bid[0].impid); + expect(response[0].width).to.equal(bidResponses.body.seatbid[0].bid[0].w); + expect(response[0].height).to.equal(bidResponses.body.seatbid[0].bid[0].h); + if (bidResponses.body.seatbid[0].bid[0].crid) { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].crid); + } else { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].id); + } + expect(response[0].currency).to.equal('USD'); + expect(response[0].ttl).to.equal(300); + expect(response[0].meta.clickUrl).to.equal('adtrue.com'); + expect(response[0].meta.advertiserDomains[0]).to.equal('adtrue.com'); + expect(response[0].ad).to.equal(bidResponses.body.seatbid[0].bid[0].adm); + expect(response[0].partnerImpId).to.equal(bidResponses.body.seatbid[0].bid[0].id); + }); + }); + describe('getUserSyncs', function () { + let USER_SYNC_URL_IFRAME = 'https://hb.adtrue.com/prebid/usersync?t=iframe&p=1212'; + let USER_SYNC_URL_IMAGE = 'https://hb.adtrue.com/prebid/usersync?t=img&p=1212'; + let sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + afterEach(function () { + sandbox.restore(); + }); + it('execute as per config', function () { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: USER_SYNC_URL_IFRAME + }]); + expect(spec.getUserSyncs({iframeEnabled: false}, {}, undefined, undefined)).to.deep.equal([{ + type: 'image', url: USER_SYNC_URL_IMAGE + }]); + }); + }); +}); From 90b2b61ad53c0b7a3596ad98beea4cb986a45f4a Mon Sep 17 00:00:00 2001 From: Filip Stamenkovic Date: Tue, 19 Jan 2021 15:46:19 +0100 Subject: [PATCH 5/7] Waardex Bid Adapter: use find function from core-js (#6200) --- modules/waardexBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/waardexBidAdapter.js b/modules/waardexBidAdapter.js index a5ba4fb0298..1558ea39cd1 100644 --- a/modules/waardexBidAdapter.js +++ b/modules/waardexBidAdapter.js @@ -2,6 +2,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import * as utils from '../src/utils.js'; +import find from 'core-js-pure/features/array/find.js'; const ENDPOINT = `https://hb.justbidit.xyz:8843/prebid`; const BIDDER_CODE = 'waardex'; @@ -182,7 +183,7 @@ const interpretResponse = (serverResponse, bidRequest) => { }; const getHbRequestBid = (openRtbBid, bidRequest) => { - return bidRequest.bidRequests.find(x => x.bidId === openRtbBid.impid); + return find(bidRequest.bidRequests, x => x.bidId === openRtbBid.impid); }; const getHbRequestMediaType = hbRequestBid => { From 4faaf470d37dcdb32e8ef6b4f72b3e0a6680d3a3 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Wed, 20 Jan 2021 06:28:43 -0800 Subject: [PATCH 6/7] Price Floors: use standard cpmAdjustment (#6194) * use standard cpmAdjustment * spaces --- modules/priceFloors.js | 2 +- test/spec/modules/priceFloors_spec.js | 35 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/modules/priceFloors.js b/modules/priceFloors.js index 7c8834c2ae2..3555fedbf3a 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -141,7 +141,7 @@ function generatePossibleEnumerations(arrayOfFields, delimiter) { * @summary If a the input bidder has a registered cpmadjustment it returns the input CPM after being adjusted */ export function getBiddersCpmAdjustment(bidderName, inputCpm, bid = {}) { - const adjustmentFunction = utils.deepAccess(getGlobal(), `bidderSettings.${bidderName}.bidCpmAdjustment`); + const adjustmentFunction = utils.deepAccess(getGlobal(), `bidderSettings.${bidderName}.bidCpmAdjustment`) || utils.deepAccess(getGlobal(), 'bidderSettings.standard.bidCpmAdjustment'); if (adjustmentFunction) { return parseFloat(adjustmentFunction(inputCpm, {...bid, cpm: inputCpm})); } diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index 984d6da1cb9..548d2789d3e 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -1316,6 +1316,41 @@ describe('the price floors module', function () { floor: 1.3334 // 1.3334 * 0.75 = 1.000005 which is the floor (we cut off getFloor at 4 decimal points) }); }); + + it('should use standard cpmAdjustment if no bidder cpmAdjustment', function () { + getGlobal().bidderSettings = { + rubicon: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + return bidResponse.cpm * 0.5; + }, + }, + standard: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + return bidResponse.cpm * 0.75; + }, + } + }; + _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[bidRequest.auctionId].data.values = { '*': 1.0 }; + let appnexusBid = { + ...bidRequest, + bidder: 'appnexus' + }; + + // the conversion should be what the bidder would need to return in order to match the actual floor + // rubicon + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 2.0 // a 2.0 bid after rubicons cpm adjustment would be 1.0 and thus is the floor after adjust + }); + + // appnexus + expect(appnexusBid.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.3334 // 1.3334 * 0.75 = 1.000005 which is the floor (we cut off getFloor at 4 decimal points) + }); + }); + it('should work when cpmAdjust function uses bid object', function () { getGlobal().bidderSettings = { rubicon: { From e33c62d652e1f7fde5d11cbc178724c709230e9c Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Wed, 20 Jan 2021 07:15:41 -0800 Subject: [PATCH 7/7] move wrapper details into wrapper object (#6184) --- modules/rubiconAnalyticsAdapter.js | 8 +++-- .../modules/rubiconAnalyticsAdapter_spec.js | 32 ++++++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 670adc9fe6d..c4d388f9277 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -162,13 +162,17 @@ function sendMessage(auctionId, bidWonId) { let message = { eventTimeMillis: Date.now(), integration: rubiConf.int_type || DEFAULT_INTEGRATION, - ruleId: rubiConf.rule_name, version: '$prebid.version$', referrerUri: referrer, referrerHostname: rubiconAdapter.referrerHostname || getHostNameFromReferer(referrer), channel: 'web', - wrapperName: rubiConf.wrapperName }; + if (rubiConf.wrapperName || rubiConf.rule_name) { + message.wrapper = { + name: rubiConf.wrapperName, + rule: rubiConf.rule_name + } + } if (auctionCache && !auctionCache.sent) { let adUnitMap = Object.keys(auctionCache.bids).reduce((adUnits, bidId) => { let bid = auctionCache.bids[bidId]; diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 78792ff2ce7..ffa7dd3c810 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -532,7 +532,9 @@ const ANALYTICS_MESSAGE = { 'bidwonStatus': 'success' } ], - 'wrapperName': '10000_fakewrapper_test' + 'wrapper': { + 'name': '10000_fakewrapper_test' + } }; function performStandardAuction(gptEvents) { @@ -1678,6 +1680,34 @@ describe('rubicon analytics adapter', function () { }); }); + describe('wrapper details passed in', () => { + it('should correctly pass in the wrapper details if provided', () => { + config.setConfig({rubicon: { + wrapperName: '1001_wrapperName', + rule_name: 'na-mobile' + }}); + + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001 + } + }); + + performStandardAuction(); + + expect(server.requests.length).to.equal(1); + const request = server.requests[0]; + const message = JSON.parse(request.requestBody); + expect(message.wrapper).to.deep.equal({ + name: '1001_wrapperName', + rule: 'na-mobile' + }); + + rubiconAnalyticsAdapter.disableAnalytics(); + }); + }); + it('getHostNameFromReferer correctly grabs hostname from an input URL', function () { let inputUrl = 'https://www.prebid.org/some/path?pbjs_debug=true'; expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.org');