UNPKG

mk9-prebid

Version:

Header Bidding Management Library

1,537 lines (1,375 loc) 90.9 kB
import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { spec } from 'modules/ixBidAdapter.js'; import { createEidsArray } from 'modules/userId/eids.js'; describe('IndexexchangeAdapter', function () { const IX_SECURE_ENDPOINT = 'https://htlb.casalemedia.com/cygnus'; const VIDEO_ENDPOINT_VERSION = 8.1; const BANNER_ENDPOINT_VERSION = 7.2; const SAMPLE_SCHAIN = { 'ver': '1.0', 'complete': 1, 'nodes': [ { 'asi': 'indirectseller.com', 'sid': '00001', 'hp': 1 }, { 'asi': 'indirectseller-2.com', 'sid': '00002', 'hp': 2 } ] }; const LARGE_SET_OF_SIZES = [ [300, 250], [600, 410], [336, 280], [400, 300], [320, 50], [360, 360], [250, 250], [320, 250], [400, 250], [387, 359], [300, 50], [372, 250], [320, 320], [412, 412], [327, 272], [312, 260], [384, 320], [335, 250], [366, 305], [374, 250], [375, 375], [272, 391], [364, 303], [414, 414], [366, 375], [272, 360], [364, 373], [366, 359], [320, 100], [360, 250], [468, 60], [480, 300], [600, 400], [600, 300], [33, 28], [40, 30], [32, 5], [36, 36], [25, 25], [320, 25], [400, 25], [387, 35], [300, 5], [372, 20], [320, 32], [412, 41], [327, 27], [312, 26], [384, 32], [335, 25], [366, 30], [374, 25], [375, 37], [272, 31], [364, 303], [414, 41], [366, 35], [272, 60], [364, 73], [366, 59], [320, 10], [360, 25], [468, 6], [480, 30], [600, 40], [600, 30] ]; const ONE_VIDEO = [ { bidder: 'ix', params: { siteId: '456', video: { skippable: false, mimes: [ 'video/mp4', 'video/webm' ], minduration: 0, maxduration: 60, protocols: [2] }, size: [400, 100] }, sizes: [[400, 100]], mediaTypes: { video: { context: 'instream', playerSize: [[400, 100]] } }, adUnitCode: 'div-gpt-ad-1460505748562-0', transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', bidId: '1a2b3c4e', bidderRequestId: '11a22b33c44e', auctionId: '1aa2bb3cc4de', schain: SAMPLE_SCHAIN } ]; const ONE_BANNER = [ { bidder: 'ix', params: { siteId: '123', size: [300, 250] }, sizes: [[300, 250]], mediaTypes: { banner: { sizes: [[300, 250]] } }, adUnitCode: 'div-gpt-ad-1460505748561-0', transactionId: '173f49a8-7549-4218-a23c-e7ba59b47229', bidId: '1a2b3c4d', bidderRequestId: '11a22b33c44d', auctionId: '1aa2bb3cc4dd', schain: SAMPLE_SCHAIN } ]; const DEFAULT_BANNER_VALID_BID = [ { bidder: 'ix', params: { siteId: '123', size: [300, 250] }, sizes: [[300, 250], [300, 600]], mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, adUnitCode: 'div-gpt-ad-1460505748561-0', transactionId: '173f49a8-7549-4218-a23c-e7ba59b47229', bidId: '1a2b3c4d', bidderRequestId: '11a22b33c44d', auctionId: '1aa2bb3cc4dd', schain: SAMPLE_SCHAIN } ]; const DEFAULT_VIDEO_VALID_BID = [ { bidder: 'ix', params: { siteId: '456', video: { skippable: false, mimes: [ 'video/mp4', 'video/webm' ], minduration: 0, maxduration: 60, protocols: [2] }, size: [400, 100] }, sizes: [[400, 100], [200, 400]], mediaTypes: { video: { context: 'instream', playerSize: [[400, 100], [200, 400]] } }, adUnitCode: 'div-gpt-ad-1460505748562-0', transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', bidId: '1a2b3c4e', bidderRequestId: '11a22b33c44e', auctionId: '1aa2bb3cc4de', schain: SAMPLE_SCHAIN } ]; const DEFAULT_MULTIFORMAT_BANNER_VALID_BID = [ { bidder: 'ix', params: { siteId: '123', size: [300, 250], }, mediaTypes: { video: { context: 'outstream', playerSize: [600, 700] }, banner: { sizes: [[300, 250], [300, 600], [400, 500]] } }, adUnitCode: 'div-gpt-ad-1460505748562-0', transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', bidId: '1a2b3c4e', bidderRequestId: '11a22b33c44e', auctionId: '1aa2bb3cc4de', schain: SAMPLE_SCHAIN } ]; const DEFAULT_MULTIFORMAT_VIDEO_VALID_BID = [ { bidder: 'ix', params: { siteId: '456', video: { skippable: false, mimes: [ 'video/mp4', 'video/webm' ], minduration: 0, maxduration: 60, protocols: [1] }, size: [300, 250] }, mediaTypes: { video: { context: 'outstream', playerSize: [300, 250] }, banner: { sizes: [[300, 250], [300, 600]] } }, adUnitCode: 'div-gpt-ad-1460505748562-0', transactionId: '273f49a8-7549-4218-a23c-e7ba59b47230', bidId: '1a2b3c4e', bidderRequestId: '11a22b33c44e', auctionId: '1aa2bb3cc4de', schain: SAMPLE_SCHAIN } ]; const DEFAULT_BANNER_BID_RESPONSE = { cur: 'USD', id: '11a22b33c44d', seatbid: [ { bid: [ { crid: '12345', adomain: ['www.abc.com'], adid: '14851455', impid: '1a2b3c4d', cid: '3051266', price: 100, w: 300, h: 250, id: '1', ext: { dspid: 50, pricelevel: '_100', advbrandid: 303325, advbrand: 'OECTA' }, adm: '<a target="_blank" href="https://www.indexexchange.com"></a>' } ], seat: '3970' } ] }; const DEFAULT_BANNER_BID_RESPONSE_WITHOUT_ADOMAIN = { cur: 'USD', id: '11a22b33c44d', seatbid: [ { bid: [ { crid: '12345', adid: '14851455', impid: '1a2b3c4d', cid: '3051266', price: 100, w: 300, h: 250, id: '1', ext: { dspid: 50, pricelevel: '_100', advbrandid: 303325, advbrand: 'OECTA' }, adm: '<a target="_blank" href="https://www.indexexchange.com"></a>' } ], seat: '3970' } ] }; const DEFAULT_VIDEO_BID_RESPONSE = { cur: 'USD', id: '1aa2bb3cc4de', seatbid: [ { bid: [ { crid: '12346', adomain: ['www.abcd.com'], adid: '14851456', impid: '1a2b3c4e', cid: '3051267', price: 110, id: '2', ext: { vasturl: 'www.abcd.com/vast', errorurl: 'www.abcd.com/error', dspid: 51, pricelevel: '_110', advbrandid: 303326, advbrand: 'OECTB' } } ], seat: '3971' } ] }; const DEFAULT_OPTION = { gdprConsent: { gdprApplies: true, consentString: '3huaa11=qu3198ae', vendorData: {} }, refererInfo: { referer: 'https://www.prebid.org', canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' } }; const DEFAULT_IDENTITY_RESPONSE = { IdentityIp: { responsePending: false, data: { source: 'identityinc.com', uids: [ { id: 'identityid' } ] } } }; const DEFAULT_BIDDER_REQUEST_DATA = { ac: 'j', r: JSON.stringify({ id: '345', imp: [ { id: '1a2b3c4e', video: { w: 640, h: 480, placement: 1 } } ], site: { ref: 'https://ref.com/ref.html', page: 'https://page.com' }, }), s: '21', sd: 1, t: 1000, v: 8.1 }; const DEFAULT_USERID_DATA = { idl_env: '1234-5678-9012-3456', // Liveramp netId: 'testnetid123', // NetId IDP: 'userIDP000', // IDP fabrickId: 'fabrickId9000', // FabrickId uid2: { id: 'testuid2' } // UID 2.0 }; const DEFAULT_USERIDASEIDS_DATA = createEidsArray(DEFAULT_USERID_DATA); const DEFAULT_USERID_PAYLOAD = [ { source: 'liveramp.com', uids: [{ id: DEFAULT_USERID_DATA.idl_env, ext: { rtiPartner: 'idl' } }] }, { source: 'netid.de', uids: [{ id: DEFAULT_USERID_DATA.netId, ext: { rtiPartner: 'NETID' } }] }, { source: 'neustar.biz', uids: [{ id: DEFAULT_USERID_DATA.fabrickId, ext: { rtiPartner: 'fabrickId' } }] }, { source: 'zeotap.com', uids: [{ id: DEFAULT_USERID_DATA.IDP, ext: { rtiPartner: 'zeotapIdPlus' } }] }, { source: 'uidapi.com', uids: [{ // when calling createEidsArray, UID2's getValue func returns .id, which is then set in uids id: DEFAULT_USERID_DATA.uid2.id, ext: { rtiPartner: 'UID2' } }] } ]; const DEFAULT_USERID_BID_DATA = { lotamePanoramaId: 'bd738d136bdaa841117fe9b331bb4', flocId: { id: '1234', version: 'chrome.1.2' } }; const DEFAULT_FLOC_USERID_PAYLOAD = [ { source: 'chrome.com', uids: [{ id: DEFAULT_USERID_BID_DATA.flocId.id, ext: { rtiPartner: 'flocId', ver: DEFAULT_USERID_BID_DATA.flocId.version } }] } ]; describe('inherited functions', function () { it('should exists and is a function', function () { const adapter = newBidder(spec); expect(adapter.callBids).to.exist.and.to.be.a('function'); }); }); describe('getUserSync tests', function () { it('UserSync test : check type = iframe, check usermatch URL', function () { const syncOptions = { 'iframeEnabled': true } let userSync = spec.getUserSyncs(syncOptions); expect(userSync[0].type).to.equal('iframe'); const USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; expect(userSync[0].url).to.equal(USER_SYNC_URL); }); it('When iframeEnabled is false, no userSync should be returned', function () { const syncOptions = { 'iframeEnabled': false } let userSync = spec.getUserSyncs(syncOptions); expect(userSync).to.be.an('array').that.is.empty; }); }); describe('isBidRequestValid', function () { it('should return true when required params found for a banner or video ad', function () { expect(spec.isBidRequestValid(DEFAULT_BANNER_VALID_BID[0])).to.equal(true); expect(spec.isBidRequestValid(DEFAULT_VIDEO_VALID_BID[0])).to.equal(true); }); it('should return true when optional bidFloor params found for an ad', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.bidFloor = 50; bid.params.bidFloorCur = 'USD'; expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return true when siteID is number', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.siteId = 123; expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return false when siteID is missing', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); delete bid.params.siteId; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return True when size is missing ', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); delete bid.params.size; expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return false when size array is wrong length', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.size = [ 300, 250, 250 ]; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when size array is array of strings', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.size = ['300', '250']; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when mediaTypes.banner does not have sizes', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.mediaTypes = { banner: { size: [[300, 250]] } }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when mediaTypes.video.playerSize does not include params.size', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes = { video: { playerSize: [300, 250] } }; bid.params.size = [100, 200]; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return true when mediaTypes.video.playerSize includes params.size', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes = { video: { playerSize: [[300, 250], [200, 300]] } }; bid.params.size = [[300, 250]]; expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return true when bid.params.size is missing', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); delete bid.params.size; expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return false when minduration is missing', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); delete bid.params.video.minduration; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when mediaType is native', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); delete bid.params.mediaTypes; bid.mediaType = 'native'; bid.sizes = [[300, 250]]; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return true when mediaType is missing and has sizes', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); delete bid.mediaTypes; bid.sizes = [[300, 250]]; expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return true when mediaType is banner', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); delete bid.mediaTypes; bid.mediaType = 'banner'; bid.sizes = [[300, 250]]; expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return true when mediaType is video', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); delete bid.mediaTypes; bid.mediaType = 'video'; bid.sizes = [[400, 100]]; expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return true for banner bid when there are multiple mediaTypes (banner, outstream)', function () { const bid = utils.deepClone(DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0]); expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return true for video bid when there are multiple mediaTypes (banner, outstream)', function () { const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return false when there is only bidFloor', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.bidFloor = 50; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when there is only bidFloorCur', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.bidFloorCur = 'USD'; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when bidFloor is string', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.bidFloor = '50'; bid.params.bidFloorCur = 'USD'; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when bidFloorCur is number', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.bidFloor = 50; bid.params.bidFloorCur = 70; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when required video properties are missing on both adunit & param levels', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); delete bid.params.video.mimes; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return true when required video properties are at the adunit level', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); delete bid.params.video.mimes; bid.mediaTypes.video.mimes = ['video/mp4']; expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return true if protocols exists but protocol doesn\'t', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); expect(spec.isBidRequestValid(bid)).to.equal(true); delete bid.params.video.protocols; bid.mediaTypes.video.protocols = 1; expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return true if protocol exists but protocols doesn\'t', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); delete bid.params.video.protocols; bid.params.video.protocol = 1; expect(spec.isBidRequestValid(bid)).to.equal(true); delete bid.params.video.protocol; bid.mediaTypes.video.protocol = 1; expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return false if both protocol/protocols are missing', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); delete bid.params.video.protocols; expect(spec.isBidRequestValid(bid)).to.equal(false); }); }); describe('Roundel alias adapter', function () { const vaildBids = [DEFAULT_BANNER_VALID_BID, DEFAULT_VIDEO_VALID_BID, DEFAULT_MULTIFORMAT_BANNER_VALID_BID, DEFAULT_MULTIFORMAT_VIDEO_VALID_BID]; const ALIAS_OPTIONS = Object.assign({ bidderCode: 'roundel' }, DEFAULT_OPTION); it('should not build requests for mediaTypes if liveramp data is unavaliable', function () { vaildBids.forEach((validBid) => { const request = spec.buildRequests(validBid, ALIAS_OPTIONS); expect(request).to.be.an('array'); expect(request).to.have.lengthOf(0); }); }); it('should build requests for mediaTypes if liveramp data is avaliable', function () { vaildBids.forEach((validBid) => { const cloneValidBid = utils.deepClone(validBid); cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); const request = spec.buildRequests(cloneValidBid, ALIAS_OPTIONS); const payload = JSON.parse(request[0].data.r); expect(request).to.be.an('array'); expect(request).to.have.lengthOf.above(0); // should be 1 or more expect(payload.user.eids).to.have.lengthOf(5); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); }); }); }); describe('buildRequestsIdentity', function () { let request; let query; let testCopy; beforeEach(function () { window.headertag = {}; window.headertag.getIdentityInfo = function () { return testCopy; }; request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; query = request.data; }); afterEach(function () { delete window.headertag; }); describe('buildRequestSingleRTI', function () { before(function () { testCopy = JSON.parse(JSON.stringify(DEFAULT_IDENTITY_RESPONSE)); }); it('payload should have correct format and value (single identity partner)', function () { const payload = JSON.parse(query.r); expect(payload.user).to.exist; expect(payload.user.eids).to.exist; expect(payload.user.eids).to.be.an('array'); expect(payload.user.eids).to.have.lengthOf(1); }); it('identity data in impression should have correct format and value (single identity partner)', function () { const impression = JSON.parse(query.r).user.eids; expect(impression[0].source).to.equal(testCopy.IdentityIp.data.source); expect(impression[0].uids[0].id).to.equal(testCopy.IdentityIp.data.uids[0].id); }); }); describe('buildRequestMultipleIds', function () { before(function () { testCopy = JSON.parse(JSON.stringify(DEFAULT_IDENTITY_RESPONSE)); testCopy.IdentityIp.data.uids.push( { id: '1234567' }, { id: '2019-04-01TF2:34:41' } ); }); it('payload should have correct format and value (single identity w/ multi ids)', function () { const payload = JSON.parse(query.r); expect(payload.user).to.exist; expect(payload.user.eids).to.exist; expect(payload.user.eids).to.be.an('array'); expect(payload.user.eids).to.have.lengthOf(1); }); it('identity data in impression should have correct format and value (single identity w/ multi ids)', function () { const impression = JSON.parse(query.r).user.eids; expect(impression[0].source).to.equal(testCopy.IdentityIp.data.source); expect(impression[0].uids).to.have.lengthOf(3); }); }); describe('buildRequestMultipleRTI', function () { before(function () { testCopy = JSON.parse(JSON.stringify(DEFAULT_IDENTITY_RESPONSE)); testCopy.JackIp = { responsePending: false, data: { source: 'jackinc.com', uids: [ { id: 'jackid' } ] } } testCopy.GenericIp = { responsePending: false, data: { source: 'genericip.com', uids: [ { id: 'genericipenvelope' } ] } } }); it('payload should have correct format and value (multiple identity partners)', function () { const payload = JSON.parse(query.r); expect(payload.user).to.exist; expect(payload.user.eids).to.exist; expect(payload.user.eids).to.be.an('array'); expect(payload.user.eids).to.have.lengthOf(3); }); it('identity data in impression should have correct format and value (multiple identity partners)', function () { const impression = JSON.parse(query.r).user.eids; expect(impression[0].source).to.equal(testCopy.IdentityIp.data.source); expect(impression[0].uids).to.have.lengthOf(1); expect(impression[1].source).to.equal(testCopy.JackIp.data.source); expect(impression[1].uids).to.have.lengthOf(1); expect(impression[2].source).to.equal(testCopy.GenericIp.data.source); expect(impression[2].uids).to.have.lengthOf(1); }); }); describe('buildRequestNoData', function () { beforeEach(function () { testCopy = JSON.parse(JSON.stringify(DEFAULT_IDENTITY_RESPONSE)); }); it('payload should not have any user eids with an undefined identity data response', function () { window.headertag.getIdentityInfo = function () { return undefined; }; request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; query = request.data; const payload = JSON.parse(query.r); expect(payload.user).to.exist; expect(payload.user.eids).to.not.exist; }); it('payload should have user eids if least one partner data is available', function () { testCopy.GenericIp = { responsePending: true, data: {} } request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; query = request.data; const payload = JSON.parse(query.r); expect(payload.user).to.exist; expect(payload.user.eids).to.exist; }); it('payload should not have any user eids if identity data is pending for all partners', function () { testCopy.IdentityIp.responsePending = true; request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; query = request.data; const payload = JSON.parse(query.r); expect(payload.user).to.exist; expect(payload.user.eids).to.not.exist; }); it('payload should not have any user eids if identity data is pending or not available for all partners', function () { testCopy.IdentityIp.responsePending = false; testCopy.IdentityIp.data = {}; request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; query = request.data; const payload = JSON.parse(query.r); expect(payload.user).to.exist; expect(payload.user.eids).to.not.exist; }); }); }); describe('buildRequestsUserId', function () { let validIdentityResponse; let validUserIdPayload; beforeEach(function () { window.headertag = {}; window.headertag.getIdentityInfo = function () { return validIdentityResponse; }; }); afterEach(function () { delete window.headertag; }); it('IX adapter reads supported user modules from Prebid and adds it to Video', function () { const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; const payload = JSON.parse(request.data.r); expect(payload.user.eids).to.have.lengthOf(5); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); }); it('IX adapter reads floc id from prebid userId and adds it to eids when there is not other eids', function () { const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_BID_DATA); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; const payload = JSON.parse(request.data.r); expect(payload.user.eids).to.have.lengthOf(1); expect(payload.user.eids).to.deep.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); }); it('IX adapter reads floc id from prebid userId and appends it to eids', function () { const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_BID_DATA); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; const payload = JSON.parse(request.data.r); expect(payload.user.eids).to.have.lengthOf(6); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); expect(payload.user.eids).to.deep.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); }); it('IX adapter reads empty floc obj from prebid userId it, floc is not added to eids', function () { const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); cloneValidBid[0].userId = { 'flocId': {} } const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; const payload = JSON.parse(request.data.r); expect(payload.user.eids).to.have.lengthOf(5); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); }); it('IX adapter reads floc obj from prebid userId it version is missing, floc is not added to eids', function () { const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); cloneValidBid[0].userId = { 'flocId': { 'id': 'abcd' } } const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; const payload = JSON.parse(request.data.r); expect(payload.user.eids).to.have.lengthOf(5); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); }); it('IX adapter reads floc obj from prebid userId it ID is missing, floc is not added to eids', function () { const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); cloneValidBid[0].userId = { 'flocId': { 'version': 'chrome.a.b.c' } } const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; const payload = JSON.parse(request.data.r); expect(payload.user.eids).to.have.lengthOf(5); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); }); it('IX adapter reads floc id with empty id from prebid userId and it does not added to eids', function () { const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); cloneValidBid[0].userId = { flocID: { id: '', ver: 'chrome.1.2.3' } }; const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; const payload = JSON.parse(request.data.r); expect(payload.user.eids).to.have.lengthOf(5); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[4]); expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); }); it('We continue to send in IXL identity info and Prebid takes precedence over IXL', function () { validIdentityResponse = { AdserverOrgIp: { responsePending: false, data: { source: 'adserver.org', uids: [ { id: '1234-5678-9012-3456', ext: { rtiPartner: 'TDID' } }, { id: 'FALSE', ext: { rtiPartner: 'TDID_LOOKUP' } }, { id: '2020-06-24T14:43:48.860Z', ext: { rtiPartner: 'TDID_CREATED_AT' } } ] } }, MerkleIp: { responsePending: false, data: { source: 'merkle.com', uids: [{ id: '1234-5678-9012-3456', ext: { keyID: '1234-5678', enc: 1 } }] } }, LiveRampIp: { source: 'liveramp.com', uids: [ { id: '0000-1234-4567-8901', ext: { rtiPartner: 'idl' } } ] }, NetIdIp: { source: 'netid.de', uids: [ { id: 'testnetid', ext: { rtiPartner: 'NETID' } } ] }, NeustarIp: { source: 'neustar.biz', uids: [ { id: 'testfabrick', ext: { rtiPartner: 'fabrickId' } } ] }, ZeotapIp: { source: 'zeotap.com', uids: [ { id: 'testzeotap', ext: { rtiPartner: 'zeotapIdPlus' } } ] } }; const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; const payload = JSON.parse(request.data.r); validUserIdPayload = utils.deepClone(DEFAULT_USERID_PAYLOAD); validUserIdPayload.push({ source: 'merkle.com', uids: [{ id: '1234-5678-9012-3456', ext: { keyID: '1234-5678', enc: 1 } }] }) validUserIdPayload.push({ source: 'adserver.org', uids: [ { id: '1234-5678-9012-3456', ext: { rtiPartner: 'TDID' } }, { id: 'FALSE', ext: { rtiPartner: 'TDID_LOOKUP' } }, { id: '2020-06-24T14:43:48.860Z', ext: { rtiPartner: 'TDID_CREATED_AT' } } ] }) expect(payload.user).to.exist; expect(payload.user.eids).to.have.lengthOf(7); expect(payload.user.eids).to.deep.include(validUserIdPayload[0]); expect(payload.user.eids).to.deep.include(validUserIdPayload[1]); expect(payload.user.eids).to.deep.include(validUserIdPayload[2]); expect(payload.user.eids).to.deep.include(validUserIdPayload[3]); expect(payload.user.eids).to.deep.include(validUserIdPayload[4]); expect(payload.user.eids).to.deep.include(validUserIdPayload[5]); expect(payload.user.eids).to.deep.include(validUserIdPayload[6]); }); it('IXL and Prebid are mutually exclusive', function () { validIdentityResponse = { LiveIntentIp: { responsePending: false, data: { source: 'liveintent.com', uids: [{ id: '1234-5678-9012-3456', ext: { keyID: '1234-5678', rtiPartner: 'LDID', enc: 1 } }] } } }; const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; validUserIdPayload = utils.deepClone(DEFAULT_USERID_PAYLOAD); validUserIdPayload.push({ source: 'liveintent.com', uids: [{ id: '1234-5678-9012-3456', ext: { keyID: '1234-5678', rtiPartner: 'LDID', enc: 1 } }] }); const payload = JSON.parse(request.data.r); expect(payload.user.eids).to.have.lengthOf(6); expect(payload.user.eids).to.deep.include(validUserIdPayload[0]); expect(payload.user.eids).to.deep.include(validUserIdPayload[1]); expect(payload.user.eids).to.deep.include(validUserIdPayload[2]); expect(payload.user.eids).to.deep.include(validUserIdPayload[3]); expect(payload.user.eids).to.deep.include(validUserIdPayload[4]); expect(payload.user.eids).to.deep.include(validUserIdPayload[5]); }); }); describe('getUserIds', function () { it('request should contain userId information if configured and within bid request', function () { config.setConfig({ userSync: { syncDelay: 0, userIds: [ { name: 'lotamePanoramaId' }, { name: 'merkleId' }, { name: 'parrableId' }, ] } }); const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.userId = DEFAULT_USERID_BID_DATA; const request = spec.buildRequests([bid], DEFAULT_OPTION)[0]; const r = JSON.parse(request.data.r); expect(r.ext.ixdiag.userIds).to.be.an('array'); expect(r.ext.ixdiag.userIds.should.include('lotamePanoramaId')); expect(r.ext.ixdiag.userIds.should.not.include('merkleId')); expect(r.ext.ixdiag.userIds.should.not.include('parrableId')); }); }); describe('First party data', function () { afterEach(function() { config.setConfig({ ortb2: {} }) }); it('should not set ixdiag.fpd value if not defined', function () { config.setConfig({ ortb2: {} }); const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; const r = JSON.parse(request.data.r); expect(r.ext.ixdiag.fpd).to.be.undefined; }); it('should set ixdiag.fpd value if it exists using fpd', function () { config.setConfig({ fpd: { site: { data: { pageType: 'article' } } } }); const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; const r = JSON.parse(request.data.r); expect(r.ext.ixdiag.fpd).to.exist; }); it('should set ixdiag.fpd value if it exists using ortb2', function () { config.setConfig({ ortb2: { site: { ext: { data: { pageType: 'article' } } } } }); const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; const r = JSON.parse(request.data.r); expect(r.ext.ixdiag.fpd).to.exist; }); it('should not send information that is not part of openRTB spec v2.5 using fpd', function () { config.setConfig({ fpd: { site: { keywords: 'power tools, drills', search: 'drill', testProperty: 'test_string' }, user: { keywords: ['a'], testProperty: 'test_string' } } }); const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; const r = JSON.parse(request.data.r); expect(r.site.keywords).to.exist; expect(r.site.search).to.exist; expect(r.site.testProperty).to.be.undefined; expect(r.user.keywords).to.exist; expect(r.user.testProperty).to.be.undefined; }); it('should not send information that is not part of openRTB spec v2.5 using ortb2', function () { config.setConfig({ ortb2: { site: { keywords: 'power tools, drills', search: 'drill', testProperty: 'test_string' }, user: { keywords: ['a'], testProperty: 'test_string' } } }); const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; const r = JSON.parse(request.data.r); expect(r.site.keywords).to.exist; expect(r.site.search).to.exist; expect(r.site.testProperty).to.be.undefined; expect(r.user.keywords).to.exist; expect(r.user.testProperty).to.be.undefined; }); it('should not add fpd data to r object if it exceeds maximum request', function () { config.setConfig({ ortb2: { site: { keywords: 'power tools, drills', search: 'drill', }, user: { keywords: Array(1000).join('#'), } } }); const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; const request = spec.buildRequests([bid])[0]; const r = JSON.parse(request.data.r); expect(r.site.ref).to.exist; expect(r.site.keywords).to.be.undefined; expect(r.user).to.be.undefined; }); }); describe('buildRequests', function () { let request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; const requestUrl = request.url; const requestMethod = request.method; const query = request.data; const bidWithoutSchain = utils.deepClone(DEFAULT_BANNER_VALID_BID); delete bidWithoutSchain[0].schain; const requestWithoutSchain = spec.buildRequests(bidWithoutSchain, DEFAULT_OPTION)[0]; const queryWithoutSchain = requestWithoutSchain.data; const bidWithoutMediaType = utils.deepClone(DEFAULT_BANNER_VALID_BID); delete bidWithoutMediaType[0].mediaTypes; bidWithoutMediaType[0].sizes = [[300, 250], [300, 600]]; const requestWithoutMediaType = spec.buildRequests(bidWithoutMediaType, DEFAULT_OPTION)[0]; const queryWithoutMediaType = requestWithoutMediaType.data; it('request should be made to IX endpoint with GET method', function () { expect(requestMethod).to.equal('GET'); expect(requestUrl).to.equal(IX_SECURE_ENDPOINT); }); it('query object (version, siteID and request) should be correct', function () { expect(query.v).to.equal(BANNER_ENDPOINT_VERSION); expect(query.s).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId); expect(query.r).to.exist; expect(query.ac).to.equal('j'); expect(query.sd).to.equal(1); expect(query.nf).not.to.exist; }); it('should send dfp_adunit_code in request if ortb2Imp.ext.data.adserver.adslot exists', function () { const AD_UNIT_CODE = '/19968336/some-adunit-path'; const validBids = utils.deepClone(DEFAULT_BANNER_VALID_BID); validBids[0].ortb2Imp = { ext: { data: { adserver: { name: 'gam', adslot: AD_UNIT_CODE } } } }; const requests = spec.buildRequests(validBids, DEFAULT_OPTION); const { dfp_ad_unit_code } = JSON.parse(requests[0].data.r).imp[0].banner.format[0].ext; expect(dfp_ad_unit_code).to.equal(AD_UNIT_CODE); }); it('should not send dfp_adunit_code in request if ortb2Imp.ext.data.adserver.adslot does not exists', function () { const validBids = utils.deepClone(DEFAULT_BANNER_VALID_BID); const requests = spec.buildRequests(validBids, DEFAULT_OPTION); const { dfp_ad_unit_code } = JSON.parse(requests[0].data.r).imp[0].banner.format[0].ext; expect(dfp_ad_unit_code).to.not.exist; }); it('payload should have correct format and value', function () { const payload = JSON.parse(query.r); expect(payload.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidderRequestId); expect(payload.id).to.be.a('string'); expect(payload.site.page).to.equal(DEFAULT_OPTION.refererInfo.referer); expect(payload.site.ref).to.equal(document.referrer); expect(payload.ext.source).to.equal('prebid'); expect(payload.source.ext.schain).to.deep.equal(SAMPLE_SCHAIN); expect(payload.imp).to.be.an('array'); expect(payload.imp).to.have.lengthOf(1); }); it('payload should have correct format and value for r.id when bidderRequestId is a number ', function () { const bidWithIntId = utils.deepClone(DEFAULT_BANNER_VALID_BID); bidWithIntId[0].bidderRequestId = 123456; request = spec.buildRequests(bidWithIntId, DEFAULT_OPTION)[0]; const payload = JSON.parse(request.data.r); expect(bidWithIntId[0].bidderRequestId).to.be.a('number'); expect(payload.id).to.equal(bidWithIntId[0].bidderRequestId.toString()); expect(payload.id).to.be.a('string'); }); it('payload should have correct format and value for r.id when bidderRequestId is a number ', function () { const bidWithIntId = utils.deepClone(DEFAULT_BANNER_VALID_BID); bidWithIntId[0].bidderRequestId = 123456; request = spec.buildRequests(bidWithIntId, DEFAULT_OPTION)[0]; const payload = JSON.parse(request.data.r); expect(bidWithIntId[0].bidderRequestId).to.be.a('number'); expect(payload.id).to.equal(bidWithIntId[0].bidderRequestId.toString()); expect(payload.id).to.be.a('string'); }); it('payload should not include schain when not provided', function () { const payload = JSON.parse(queryWithoutSchain.r); expect(payload.source).to.not.exist; // source object currently only written for schain }); it('impression should have correct format and value', function () { const impression = JSON.parse(query.r).imp[0]; expect(impression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); expect(impression.banner.format).to.be.length(2); expect(impression.banner.topframe).to.be.oneOf([0, 1]); impression.banner.format.map(({ w, h, ext }, index) => { const size = DEFAULT_BANNER_VALID_BID[0].mediaTypes.banner.sizes[index]; const sidValue = utils.parseGPTSingleSizeArray(size); expect(w).to.equal(size[0]); expect(h).to.equal(size[1]); expect(ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()); expect(ext.sid).to.equal(sidValue); }); }); describe('build requests with price floors', () => { const highFloor = 4.5; const lowFloor = 3.5; const currency = 'USD'; it('video impression should contain floors from priceFloors module', function () { const bid = utils.deepClone(ONE_VIDEO[0]); const expectedFloor = 3.25; bid.getFloor = () => ({ floor: expectedFloor, currency }); const request = spec.buildRequests([bid])[0]; const impression = JSON.parse(request.data.r).imp[0]; expect(impression.bidfloor).to.equal(expectedFloor); expect(impression.bidfloorcur).to.equal(currency); expect(impression.ext.fl).to.equal('p'); }); it('banner impression should contain floors from priceFloors module', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]) const expectedFloor = 3.25; bid.getFloor = () => ({ floor: expectedFloor, currency }); const request = spec.buildRequests([bid])[0]; const impression = JSON.parse(request.data.r).imp[0]; expect(impression.bidfloor).to.equal(expectedFloor); expect(impression.bidfloorcur).to.equal(currency); expect(impression.banner.format[0].ext.fl).to.equal('p'); }); it('should default to ix floors when priceFloors Module is not implemented', function () { const bid = utils.deepClone(ONE_BANNER[0]); bid.params.bidFloor = highFloor; bid.params.bidFloorCur = 'USD' const request = spec.buildRequests([bid])[0]; const impression = JSON.parse(request.data.r).imp[0]; expect(impression.bidfloor).to.equal(highFloor); expect(impression.bidfloorcur).to.equal(bid.params.bidFloorCur); expect(impression.banner.format[0].ext.fl).to.equal('x'); }); it('should prioritize priceFloors Module over IX param floors', function () { const bid = utils.deepClone(ONE_BANNER[0]); bid.params.bidFloor = lowFloor; bid.params.bidFloorCur = 'USD'; const expectedFloor = highFloor; bid.getFloor = () => ({ floor: expectedFloor, currency }); const requestBidFloor = spec.buildRequests([bid])[0]; const impression = JSON.parse(requestBidFloor.data.r).imp[0]; expect(impression.bidfloor).to.equal(highFloor); expect(impression.bidfloorcur).to.equal(bid.params.bidFloorCur); expect(impression.banner.format[0].ext.fl).to.equal('p'); }); it('impression should have bidFloor and bidFloorCur if configured', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bi