diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index eb8f310201d9..7aa853ad902c 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -1,461 +1,1485 @@ -import { expect, assert } from 'chai'; +import { expect } from 'chai'; import { spec } from 'modules/kargoBidAdapter.js'; import { config } from 'src/config.js'; const utils = require('src/utils'); -describe('kargo adapter tests', function () { - var sandbox, clock, frozenNow = new Date(); - const testSchain = { - complete: 1, - nodes: [ +describe('kargo adapter tests', function() { + let bid, outstreamBid, testBids, sandbox, clock, frozenNow = new Date(), oldBidderSettings; + + const topUrl = 'https://random.com/this/is/a/url'; + const domain = 'random.com'; + const referer = 'https://random.com/'; + + const defaultBidParams = Object.freeze({ + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + getFloor: () => {}, + ortb2: { + device: { + w: 1720, + h: 1000, + dnt: 0, + language: 'en', + ua: 'Mozilla/5.0' + }, + site: { + domain: domain, + mobile: 0, + page: topUrl, + publisher: { + domain: domain + }, + ref: referer + }, + source: { + tid: 'random-tid' + } + }, + ortb2Imp: { + ext: { + data: { + pbadslot: '/1234/prebid/slot/path' + }, + gpid: '/1234/prebid/slot/path', + } + }, + userId: { + tdid: 'random-tdid' + }, + userIdAsEids: [ + { + source: 'adquery.io', + uids: [ { + id: 'adquery-id', + atype: 1 + } ] + }, + { + source: 'criteo.com', + uids: [ { + id: 'criteo-id', + atype: 1 + } ] + }, { - 'asi': 'test-page.com', - 'hp': 1, - 'rid': '57bdd953-6e57-4d5b-9351-ed67ca238890', - 'sid': '8190248274' + source: 'adserver.org', + uids: [ { + id: 'adserver-id', + atype: 1, + ext: { rtiPartner: 'TDID' } + } ] + }, + ], + floorData: { + floorMin: 1 + }, + schain: { + validation: 'strict', + config: { + ver: '1.0', + complete: 1, + nodes: [ { + asi: 'indirectseller.com', + sid: '00001', + hp: 1, + } ] + } + }, + }); + + const minimumBidParams = Object.freeze({ + params: { + placementId: 'foobar' + }, + mediaTypes: { + banner: { sizes: [ [1, 1] ] } + } + }); + + const validCrbIds = { + 2: '82fa2555-5969-4614-b4ce-4dcf1080e9f9', + 16: 'VoxIk8AoJz0AAEdCeyAAAAC2&502', + 23: 'd2a855a5-1b1c-4300-940e-a708fa1f1bde', + 24: 'VoxIk8AoJz0AAEdCeyAAAAC2&502', + 25: '5ee24138-5e03-4b9d-a953-38e833f2849f', + '2_80': 'd2a855a5-1b1c-4300-940e-a708fa1f1bde', + '2_93': '5ee24138-5e03-4b9d-a953-38e833f2849f' + }; + const validCrbIdsLs = { + 2: '82fa2555-5969-4614-b4ce-4dcf1080e9f9', + 16: 'VoxIk8AoJz0AAEdCeyAAAAC2&502', + 23: 'd2a855a5-1b1c-4300-940e-a708fa1f1bde', + 25: '5ee24138-5e03-4b9d-a953-38e833f2849f', + '2_80': 'd2a855a5-1b1c-4300-940e-a708fa1f1bde', + '2_93': '5ee24138-5e03-4b9d-a953-38e833f2849f' + }; + function buildCrbValue(isCookie, withIds, withTdid, withLexId, withClientId, optOut) { + let value = { + expireTime: Date.now() + 60000, + lastSyncedAt: Date.now() - 60000, + optOut, + }; + const locationString = isCookie ? 'cookie' : 'localstorage'; + + if (withIds) { + if (isCookie) { + value.syncIds = validCrbIds; + } else { + value.syncIds = validCrbIdsLs; } - ] + } + if (withTdid) { + value.tdID = `test-tdid-cerberus-${locationString}`; + } + if (withLexId) { + value.lexId = `test-lexid-cerberus-${locationString}`; + } + if (withClientId) { + value.clientId = `test-clientid-cerberus-${locationString}`; + } + + const b64Value = btoa(JSON.stringify(value)); + if (isCookie) { + return JSON.stringify({ v: b64Value }); + } + return b64Value; } - beforeEach(function () { + beforeEach(function() { + oldBidderSettings = $$PREBID_GLOBAL$$.bidderSettings; + $$PREBID_GLOBAL$$.bidderSettings = { + kargo: { storageAllowed: true } + }; + + bid = { + ...defaultBidParams, + bidder: 'kargo', + params: { + placementId: 'displayPlacement' + }, + mediaTypes: { + banner: { + sizes: [ [970, 250], [1, 1] ] + } + }, + adUnitCode: 'displayAdunitCode', + sizes: [ [300, 250], [300, 600] ], + bidId: 'randomBidId', + bidderRequestId: 'randomBidderRequestId', + auctionId: 'randomAuctionId' + }; + + outstreamBid = { + ...defaultBidParams, + bidder: 'kargo', + params: { + placementId: 'instreamPlacement' + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + api: [ 1, 2 ], + linearity: 1, + maxduration: 60, + mimes: [ 'video/mp4', 'video/webm', 'application/javascript' ], + minduration: 0, + placement: 1, + playbackmethod: [ 1, 2, 3 ], + plcmt: 1, + protocols: [ 2, 3, 5 ], + skip: 1 + } + }, + adUnitCode: 'instreamAdunitCode', + sizes: [ [300, 250], [300, 600] ], + bidId: 'randomBidId2', + bidderRequestId: 'randomBidderRequestId2', + auctionId: 'randomAuctionId2' + }; + + testBids = [{ ...minimumBidParams }]; + sandbox = sinon.sandbox.create(); clock = sinon.useFakeTimers(frozenNow.getTime()); }); - afterEach(function () { + afterEach(function() { sandbox.restore(); clock.restore(); + $$PREBID_GLOBAL$$.bidderSettings = oldBidderSettings; }); - describe('bid request validity', function () { - it('passes when the bid includes a placement ID', function () { - assert(spec.isBidRequestValid({ params: { placementId: 'foo' } }) === true); + describe('gvlid', function() { + it('exposes the gvlid (global vendor list ID) of 972', function() { + expect(spec.gvlid).to.exist.and.equal(972); + }); + }); + + describe('code', function() { + it('exposes the code kargo', function() { + expect(spec.code).to.exist.and.equal('kargo'); + }); + }); + + describe('isBidRequestValid', function() { + it('fails when the bid object is undefined', function() { + expect(spec.isBidRequestValid()).to.equal(false); }); - it('fails when the bid does not include a placement ID', function () { - assert(spec.isBidRequestValid({ params: {} }) === false); + it('fails when the bid object has no keys at all', function() { + expect(spec.isBidRequestValid({})).to.equal(false); }); - it('fails when bid is falsey', function () { - assert(spec.isBidRequestValid() === false); + it('fails when the bid includes params but not a placement ID', function() { + expect(spec.isBidRequestValid({ params: {} })).to.equal(false); }); - it('fails when the bid has no params at all', function () { - assert(spec.isBidRequestValid({}) === false); + it('passes when the bid includes a placement ID', function () { + expect(spec.isBidRequestValid({ params: { placementId: 'foo' } })).to.equal(true); + }); + + it('passes when the bid includes a placement ID and other keys', function() { + expect(spec.isBidRequestValid({ params: { placementId: 'foo', other: 'value' } })).to.equal(true); + }); + + it('passes when the full bid information is provided', function() { + expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(outstreamBid)).to.equal(true); }); }); - describe('build request', function () { - var bids, undefinedCurrency, noAdServerCurrency, nonUSDAdServerCurrency, cookies = [], localStorageItems = [], sessionIds = [], requestCount = 0; + describe('buildRequests', function() { + let bids, + bidderRequest, + undefinedCurrency, + noAdServerCurrency, + nonUSDAdServerCurrency, + cookies = [], + localStorageItems = [], + session_id = null; + + before(function() { + sinon.spy(spec, 'buildRequests'); + }); - beforeEach(function () { - $$PREBID_GLOBAL$$.bidderSettings = { - kargo: { - storageAllowed: true - } - }; + beforeEach(function() { undefinedCurrency = false; noAdServerCurrency = false; nonUSDAdServerCurrency = false; sandbox.stub(config, 'getConfig').callsFake(function (key) { if (key === 'currency') { - if (undefinedCurrency) { - return undefined; - } - if (noAdServerCurrency) { - return {}; - } - if (nonUSDAdServerCurrency) { - return { adServerCurrency: 'EUR' }; - } + if (undefinedCurrency) return undefined; + + if (noAdServerCurrency) return {}; + + if (nonUSDAdServerCurrency) return { adServerCurrency: 'EUR' }; + return { adServerCurrency: 'USD' }; } + if (key === 'debug') return true; if (key === 'deviceAccess') return true; - throw new Error(`Config stub incomplete! Missing key "${key}"`) + throw new Error(`Config stub incomplete, missing key "${key}"`); }); bids = [ - { - params: { - placementId: 'foo', - socialCanvas: { - segments: ['segment_1', 'segment_2', 'segment_3'], - url: 'https://socan.url' - } - }, - auctionId: '1234098', - bidId: '1', - adUnitCode: '101', - sizes: [[320, 50], [300, 250], [300, 600]], - mediaTypes: { - banner: { - sizes: [[320, 50], [300, 50]] - } - }, - bidRequestsCount: 1, - bidderRequestsCount: 2, - bidderWinsCount: 3, - schain: testSchain, - userId: { - tdid: 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c' + bid, + outstreamBid + ]; + + bidderRequest = { + bidderCode: 'kargo', + auctionId: 'test-auction-id', + bidderRequestId: 'test-request-id', + bids, + ortb2: defaultBidParams.ortb2, + refererInfo: { + canonicalUrl: topUrl, + domain, + isAmp: false, + location: topUrl, + numIframs: 0, + page: topUrl, + reachedTop: true, + ref: referer, + stack: [ topUrl ], + topmostLocation: topUrl, + }, + start: Date.now(), + timeout: 2500, + }; + }); + afterEach(function() { + cookies.forEach(key => document.cookie = `${key}=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/`); + cookies = []; + + localStorageItems.forEach(key => localStorage.removeItem(key)); + localStorageItems = []; + }); + + function getPayloadFromTestBids(testBids) { + const request = spec.buildRequests(testBids, { ...bidderRequest, bids: testBids }); + const payload = request.data; + if (session_id === null) session_id = spec._getSessionId(); + + expect(payload).to.exist; + expect(payload.imp).to.have.length(testBids.length); + expect(payload.requestCount).to.equal(spec.buildRequests.callCount - 1); + expect(payload.sid).to.equal(session_id); + + return payload; + } + + function setCookieValue(name, value) { + cookies.push(name); + document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; max-age-in-seconds=1000; path=/`; + } + + function setLocalStorageValue(name, value) { + localStorageItems.push(name); + localStorage.setItem(name, value); + } + + const crbValues = { + valid: buildCrbValue(true, true, true, true, true, false), + validLs: buildCrbValue(false, true, true, true, true, false), + invalidB64: 'crbValue', + invalidB64Ls: 'crbValueLs', + invalidJson: 'eyJ0ZXN0OiJ2YWx1ZSJ9', + invalidJsonLs: 'eyJ0ZXN0OiJ2YWx1ZSJ9', + }; + function setCrb( + setCookie = 'valid', + setLocalStorage = 'valid' + ) { + if (crbValues.hasOwnProperty(setCookie)) { + setCookieValue('krg_crb', crbValues[setCookie]); + } + if (crbValues.hasOwnProperty(`${setLocalStorage}Ls`)) { + setLocalStorageValue('krg_crb', crbValues[`${setLocalStorage}Ls`]); + } + } + + it('exists and produces an object', function() { + const request = spec.buildRequests(bids, bidderRequest); + expect(request).to.exist.and.to.be.an('object'); + }); + + it('produces a POST request with a payload', function() { + const request = spec.buildRequests(bids, bidderRequest); + expect(request.method).to.exist.and.equal('POST'); + expect(request.url).to.exist.and.equal('https://krk2.kargo.com/api/v1/prebid'); + + const payload = request.data; + expect(payload).to.exist; + expect(payload.imp).to.have.length(2); + + // Display bid + const bidImp = payload.imp[0]; + expect(bidImp.id).to.equal('randomBidId'); + expect(bidImp.banner).to.deep.equal({ sizes: [ [970, 250], [1, 1] ] }); + expect(bidImp.video).to.be.undefined; + expect(bidImp.bidRequestCount).to.equal(1); + expect(bidImp.bidderRequestCount).to.equal(1); + expect(bidImp.code).to.equal('displayAdunitCode'); + expect(bidImp.ext.ortb2Imp).to.deep.equal(defaultBidParams.ortb2Imp); + expect(bidImp.fpd).to.deep.equal({ gpid: '/1234/prebid/slot/path' }); + expect(bidImp.pid).to.equal('displayPlacement'); + + // Video bid + const videoBidImp = payload.imp[1]; + expect(videoBidImp.id).to.equal('randomBidId2'); + expect(videoBidImp.banner).to.be.undefined; + expect(videoBidImp.video).to.deep.equal(outstreamBid.mediaTypes.video); + expect(videoBidImp.bidRequestCount).to.equal(1); + expect(videoBidImp.bidderRequestCount).to.equal(1); + expect(videoBidImp.code).to.equal('instreamAdunitCode'); + expect(videoBidImp.ext.ortb2Imp).to.deep.equal(defaultBidParams.ortb2Imp); + expect(videoBidImp.fpd).to.deep.equal({ gpid: '/1234/prebid/slot/path' }); + expect(videoBidImp.pid).to.equal('instreamPlacement'); + + // User + expect(payload.user).to.be.an('object'); + expect(payload.user.crbIDs).to.deep.equal({}); + expect(payload.user.data).to.deep.equal([]); + expect(payload.user.sharedIDEids).to.deep.equal(defaultBidParams.userIdAsEids); + + // General keys + expect(payload.aid).to.equal('randomAuctionId'); + expect(payload.device).to.deep.equal({ size: [ window.screen.width, window.screen.height ] }); + expect(payload.ext.ortb2).to.deep.equal(defaultBidParams.ortb2); + expect(payload.pbv).to.equal('$prebid.version$'); + expect(payload.requestCount).to.equal(spec.buildRequests.callCount - 1); + expect(payload.sid).to.be.a('string').with.length(36); + expect(payload.timeout).to.equal(2500); + expect(payload.url).to.equal(topUrl); + expect(payload.ts).to.be.a('number'); + }); + + it('copies the ortb2 object from the first bid request if present', function() { + let payload; + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + ortb2: { + user: { + key: 'value' + } + } + }]); + expect(payload.ext).to.deep.equal({ ortb2: { + user: { key: 'value' } + }}); + + payload = getPayloadFromTestBids(testBids); + expect(payload.ext).to.be.undefined; + + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + ortb2: { + user: { + key: 'value' + } + } + }, { + ...minimumBidParams, + ortb2: { + site: { + key2: 'value2' + } + } + }]); + expect(payload.ext).to.deep.equal({ortb2: { + user: { key: 'value' } + }}); + }); + + it('pulls the site category from the first bids ortb2 object', function() { + let payload; + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + ortb2: { site: { cat: 'test-cat' } } + }]); + expect(payload.site).to.deep.equal({ cat: 'test-cat' }); + + payload = getPayloadFromTestBids(testBids); + expect(payload.site).to.be.undefined; + + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + ortb2: { site: { cat: 'test-cat' } } + }, { + ...minimumBidParams, + ortb2: { site: { cat: 'test-cat-2' } } + }]); + expect(payload.site).to.deep.equal({ cat: 'test-cat' }); + }); + + it('pulls the schain from the first bid if it is populated', function() { + let payload; + payload = getPayloadFromTestBids(testBids); + expect(payload.schain).to.be.undefined; + + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + schain: {} + }, { + ...minimumBidParams, + schain: { + complete: 1, + nodes: [{ + asi: 'test-page.com', + hp: 1, + rid: '57bdd953-6e57-4d5b-9351-ed67ca238890', + sid: '8190248274' + }] + } + }]); + expect(payload.schain).to.be.undefined; + + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + schain: { + complete: 1, + nodes: [{ + asi: 'test-page.com', + hp: 1, + rid: '57bdd953-6e57-4d5b-9351-ed67ca238890', + sid: '8190248274' + }] + } + }, { + ...minimumBidParams, + schain: { + complete: 1, + nodes: [{ + asi: 'test-page-2.com', + hp: 1, + rid: 'other-rid', + sid: 'other-sid' + }] + } + }]); + expect(payload.schain).to.deep.equal({ + complete: 1, + nodes: [{ + asi: 'test-page.com', + hp: 1, + rid: '57bdd953-6e57-4d5b-9351-ed67ca238890', + sid: '8190248274' + }] + }); + }); + + it('does not send currency if it is not defined', function() { + undefinedCurrency = true; + let payload = getPayloadFromTestBids(testBids); + expect(payload.cur).to.be.undefined; + }); + + it('does not send currency if it is missing', function() { + noAdServerCurrency = true; + let payload = getPayloadFromTestBids(testBids); + expect(payload.cur).to.be.undefined; + }); + + it('does not send currency if it is USD', function() { + let payload = getPayloadFromTestBids(testBids); + expect(payload.cur).to.be.undefined; + }); + + it('provides the currency if it is not USD', function() { + nonUSDAdServerCurrency = true; + let payload = getPayloadFromTestBids(testBids); + expect(payload.cur).to.equal('EUR'); + }); + + it('provides the social canvas segments and URL if provided', function() { + let payload; + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + }, { + ...minimumBidParams, + params: { + ...minimumBidParams.params, + socialCanvas: { + segments: ['segment_1', 'segment_2', 'segment_3'], + url: 'https://socan.url' + } + } + }]); + expect(payload.socan).to.be.undefined; + + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + params: { + ...minimumBidParams.params, + socialCanvas: null + } + }, { + ...minimumBidParams, + params: { + ...minimumBidParams.params, + socialCanvas: { + segments: ['segment_1', 'segment_2', 'segment_3'], + url: 'https://socan.url' + } + } + }]); + expect(payload.socan).to.be.undefined; + + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + params: { + ...minimumBidParams.params, + socialCanvas: { + segments: ['segment_1', 'segment_2', 'segment_3'], + url: 'https://socan.url' + } + } + }, { + ...minimumBidParams, + params: { + ...minimumBidParams.params, + socialCanvas: { + segments: ['segment_4', 'segment_5', 'segment_6'], + url: 'https://socan.url/new' + } + } + }]); + expect(payload.socan).to.deep.equal({ + segments: ['segment_1', 'segment_2', 'segment_3'], + url: 'https://socan.url' + }); + }); + + describe('imp', function() { + it('handles slots with different combinations of formats', function() { + const testBids = [ + // Banner and Outstream + { + ...bid, + params: { + inventoryCode: 'banner_outstream_test', + floor: 1.0, + video: { + mimes: [ 'video/mp4' ], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 380] + }, + banner: { + sizes: [ [970, 250], [1, 1] ] + } + }, + adUnitCode: 'adunit-code-banner-outstream', + sizes: [ [300, 250], [300, 600], [1, 1, 1], ['flex'] ], + bidId: 'banner-outstream-bid-id', + bidderRequestId: 'kargo-request-id', + auctionId: 'kargo-auction-id', }, - userIdAsEids: [ - { - 'source': 'adserver.org', - 'uids': [ - { - 'id': 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c', - 'atype': 1, - 'ext': { - 'rtiPartner': 'TDID' - } - } - ] + // Banner and Native + { + ...bid, + params: { + inventoryCode: 'banner_outstream_test', + floor: 1.0, + video: { + mimes: [ 'video/mp4' ], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } }, - { - 'source': 'adquery.io', - 'uids': [ - { - 'id': 'adqueryId-123', - 'atype': 1 - } - ] + mediaTypes: { + banner: { + sizes: [ [970, 250], [1, 1] ] + }, + native: {} }, - { - 'source': 'criteo.com', - 'uids': [ - { - 'id': 'criteoId-456', - 'atype': 1 - } - ] - } - ], - floorData: { - floorMin: 1 + adUnitCode: 'adunit-code-banner-outstream', + sizes: [ [300, 250], [300, 600], [1, 1, 1], ['flex'] ], + bidId: 'banner-outstream-bid-id', + bidderRequestId: 'kargo-request-id', + auctionId: 'kargo-auction-id', }, - ortb2: { - device: { - sua: { - platform: { - brand: 'macOS', - version: ['12', '6', '0'] - }, - browsers: [ - { - brand: 'Chromium', - version: ['106', '0', '5249', '119'] - }, - { - brand: 'Google Chrome', - version: ['106', '0', '5249', '119'] - }, - { - brand: 'Not;A=Brand', - version: ['99', '0', '0', '0'] - } - ], - mobile: 1, - model: 'model', - source: 1, + // Native and Outstream + { + ...bid, + params: { + inventoryCode: 'banner_outstream_test', + floor: 1.0, + video: { + mimes: [ 'video/mp4' ], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 } }, - site: { - id: '1234', - name: 'SiteName', - cat: ['IAB1', 'IAB2', 'IAB3'] + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 380] + }, + native: {}, }, - user: { - data: [ - { - name: 'prebid.org', - ext: { - segtax: 600, - segclass: 'v1', - }, - segment: [ - { - id: '133' - }, - ] - }, - ] - } + adUnitCode: 'adunit-code-banner-outstream', + sizes: [ [300, 250], [300, 600], [1, 1, 1], ['flex'] ], + bidId: 'banner-outstream-bid-id', + bidderRequestId: 'kargo-request-id', + auctionId: 'kargo-auction-id', }, - ortb2Imp: { - ext: { - tid: '10101', - data: { - adServer: { - name: 'gam', - adslot: '/22558409563,18834096/dfy_mobile_adhesion' - }, - pbadslot: '/22558409563,18834096/dfy_mobile_adhesion' + // Banner and Native and Outstream + { + ...bid, + params: { + inventoryCode: 'banner_outstream_test', + floor: 1.0, + video: { + mimes: [ 'video/mp4' ], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 380] }, - gpid: '/22558409563,18834096/dfy_mobile_adhesion' - } - } - }, - { - params: { - placementId: 'bar' + banner: { + sizes: [ [970, 250], [1, 1] ] + }, + native: {}, + }, + adUnitCode: 'adunit-code-banner-outstream', + sizes: [ [300, 250], [300, 600], [1, 1, 1], ['flex'] ], + bidId: 'banner-outstream-bid-id', + bidderRequestId: 'kargo-request-id', + auctionId: 'kargo-auction-id', }, - bidId: '2', - adUnitCode: '202', - sizes: [[320, 50], [300, 250], [300, 600]], - mediaTypes: { - video: { - sizes: [[320, 50], [300, 50]] + ]; + + const payload = getPayloadFromTestBids(testBids); + + const bannerImp = { + sizes: [ [970, 250], [1, 1] ] + }; + const videoImp = { + context: 'outstream', + playerSize: [640, 380] + }; + const nativeImp = {}; + + // Banner and Outstream + expect(payload.imp[0].banner).to.deep.equal(bannerImp); + expect(payload.imp[0].video).to.deep.equal(videoImp); + expect(payload.imp[0].native).to.be.undefined; + // Banner and Native + expect(payload.imp[1].banner).to.deep.equal(bannerImp); + expect(payload.imp[1].video).to.be.undefined; + expect(payload.imp[1].native).to.deep.equal(nativeImp); + // Native and Outstream + expect(payload.imp[2].banner).to.be.undefined; + expect(payload.imp[2].video).to.deep.equal(videoImp); + expect(payload.imp[2].native).to.deep.equal(nativeImp); + // Banner and Native and Outstream + expect(payload.imp[3].banner).to.deep.equal(bannerImp); + expect(payload.imp[3].video).to.deep.equal(videoImp); + expect(payload.imp[3].native).to.deep.equal(nativeImp); + }); + + it('pulls gpid from ortb2Imp.ext.gpid then ortb2Imp.ext.data.pbadslot', function () { + const gpidGpid = 'ortb2Imp.ext.gpid-gpid'; + const gpidPbadslot = 'ortb2Imp.ext.data.pbadslot-gpid' + const testBids = [ + { + ...minimumBidParams, + ortb2Imp: { + ext: { + gpid: gpidGpid, + data: { + pbadslot: gpidPbadslot + } + } } }, - bidRequestsCount: 0, - bidderRequestsCount: 0, - bidderWinsCount: 0, - ortb2Imp: { - ext: { - tid: '20202', - data: { - adServer: { - name: 'gam', - adslot: '/22558409563,18834096/dfy_mobile_adhesion' - }, - pbadslot: '/22558409563,18834096/dfy_mobile_adhesion' + { + ...minimumBidParams, + ortb2Imp: { + ext: { + gpid: gpidGpid, + data: {}, } } - } - }, - { - params: { - placementId: 'bar' }, - bidId: '3', - adUnitCode: '303', - sizes: [[320, 50], [300, 250], [300, 600]], - mediaTypes: { - native: { - sizes: [[320, 50], [300, 50]] + { + ...minimumBidParams, + ortb2Imp: { + ext: { + data: { + pbadslot: gpidPbadslot + } + } } }, - ortb2Imp: { - ext: { - tid: '30303', - data: { - adServer: { - name: 'gam', - adslot: '/22558409563,18834096/dfy_mobile_adhesion' + { + ...minimumBidParams, + ortb2Imp: { + ext: { + gpid: null, + data: { + pbadslot: null } - }, - gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } } - } + }, + { ...minimumBidParams } + ]; + + const payload = getPayloadFromTestBids(testBids); + + // Both present + expect(payload.imp[0].fpd).to.deep.equal({ gpid: gpidGpid }); + // Only ext.gpid + expect(payload.imp[1].fpd).to.deep.equal({ gpid: gpidGpid }); + // Only ext.data.pbadslot + expect(payload.imp[2].fpd).to.deep.equal({ gpid: gpidPbadslot }); + // Neither present + expect(payload.imp[3].fpd).to.be.undefined; + expect(payload.imp[4].fpd).to.be.undefined; + }); + + it('includes bidRequestCount, bidderRequestCount, and bidderWinsCount if they are greater than 0', function() { + const testBids = [ + { + ...minimumBidParams, + bidRequestsCount: 1, + bidderRequestsCount: 0, + bidderWinsCount: 0, + }, + { + ...minimumBidParams, + bidRequestsCount: 0, + bidderRequestsCount: 2, + bidderWinsCount: 0, + }, + { + ...minimumBidParams, + bidRequestsCount: 0, + bidderRequestsCount: 0, + bidderWinsCount: 3, + }, + { + ...minimumBidParams, + bidRequestsCount: 4, + bidderRequestsCount: 1, + bidderWinsCount: 3, + }, + ]; + + [ 0, null, false, 'foobar' ].forEach(value => testBids.push({ + ...minimumBidParams, + bidRequestsCount: value, + bidderRequestsCount: value, + bidderWinsCount: value + })); + + const payload = getPayloadFromTestBids(testBids); + + // bidRequestCount + expect(payload.imp[0].bidRequestCount).to.equal(1); + expect(payload.imp[0].bidderRequestCount).to.be.undefined; + expect(payload.imp[0].bidderWinCount).to.be.undefined; + // bidderRequestCount + expect(payload.imp[1].bidRequestCount).to.be.undefined; + expect(payload.imp[1].bidderRequestCount).to.equal(2); + expect(payload.imp[1].bidderWinCount).to.be.undefined; + // bidderWinCount + expect(payload.imp[2].bidRequestCount).to.be.undefined; + expect(payload.imp[2].bidderRequestCount).to.be.undefined; + expect(payload.imp[2].bidderWinCount).to.equal(3); + // all + expect(payload.imp[3].bidRequestCount).to.equal(4); + expect(payload.imp[3].bidderRequestCount).to.equal(1); + expect(payload.imp[3].bidderWinCount).to.equal(3); + // none + for (let i = 4; i < payload.imp.length; i++) { + expect(payload.imp[i].bidRequestCount).to.be.undefined; + expect(payload.imp[i].bidderRequestCount).to.be.undefined; + expect(payload.imp[i].bidderWinCount).to.be.undefined; } - ]; - }); + }); - afterEach(function () { - for (let key in cookies) { - let cookie = cookies[key]; - removeCookie(cookie); - } + it('queries the getFloor function to retrieve the floor and validates it', function() { + const testBids = []; + + [ + { currency: 'USD', floor: 1.99 }, + { currency: 'USD', floor: '1.99' }, + { currency: 'EUR', floor: 1.99 }, + { currency: 'USD', floor: 'foo' }, + { currency: 'USD', floor: null }, + { currency: 'USD', floor: true }, + { currency: 'USD', floor: false }, + { currency: 'USD', floor: {} }, + { currency: 'USD', floor: [] }, + ].forEach(floorValue => testBids.push({ + ...minimumBidParams, + getFloor: () => floorValue + })); + + const payload = getPayloadFromTestBids(testBids); + + // Valid floor + expect(payload.imp[0].floor).to.equal(1.99); + // Valid floor but string + expect(payload.imp[1].floor).to.equal('1.99'); // @TODO - convert this to a number? + // Non-USD valid floor + expect(payload.imp[2].floor).to.be.undefined; + // Invalid floor + for (let i = 3; i < payload.imp.length; i++) { + expect(payload.imp[i].floor).to.be.undefined; + } + }); - for (let key in localStorageItems) { - let localStorageItem = localStorageItems[key]; - localStorage.removeItem(localStorageItem); - } + it('calls getFloor with the right values', function() { + const testBids = [ + { + ...minimumBidParams, + getFloor: () => ({ currency: 'USD', value: 0.5 }) + } + ]; + sinon.spy(testBids[0], 'getFloor'); - cookies.length = 0; - localStorageItems.length = 0; - $$PREBID_GLOBAL$$.bidderSettings = {}; + getPayloadFromTestBids(testBids); + + expect(testBids[0].getFloor.calledWith({ + currency: 'USD', + mediaType: '*', + size: '*' + })).to.be.true; + }); }); - function setCookie(cname, cvalue, exdays = 1) { - _setCookie(cname, cvalue, exdays); - cookies.push(cname); - } + describe('cerberus', function() { + it('retrieves CRB from localStorage and cookies', function() { + setCrb('valid', 'valid'); - function removeCookie(cname) { - _setCookie(cname, '', -1); - } + const payload = getPayloadFromTestBids(testBids, bidderRequest); - function _setCookie(cname, cvalue, exdays = 1) { - var d = new Date(), - expires; + expect(payload.rawCRB).to.equal(crbValues.valid); + expect(payload.rawCRBLocalStorage).to.equal(crbValues.validLs); + expect(payload.user.crbIDs).to.deep.equal(validCrbIdsLs); + expect(payload.user.tdID).to.equal('test-tdid-cerberus-localstorage'); + expect(payload.user.kargoID).to.equal('test-lexid-cerberus-localstorage'); + expect(payload.user.clientID).to.equal('test-clientid-cerberus-localstorage'); + expect(payload.user.optOut).to.equal(false); + }); - d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); - expires = `expires=${d.toUTCString()}`; - document.cookie = `${cname}=${cvalue};${expires};path=/`; - } + it('retrieves CRB from localStorage if cookie is missing', function() { + setCrb(false, 'valid'); - function setLocalStorageItem(name, val) { - localStorage.setItem(name, val); - localStorageItems.push(name); - } + const payload = getPayloadFromTestBids(testBids, bidderRequest); - function simulateNoLocalStorage() { - return sandbox.stub(localStorage, 'getItem').throws(); - } + expect(payload.rawCRB).to.be.undefined; + expect(payload.rawCRBLocalStorage).to.equal(crbValues.validLs); + expect(payload.user.crbIDs).to.deep.equal(validCrbIdsLs); + expect(payload.user.tdID).to.equal('test-tdid-cerberus-localstorage'); + expect(payload.user.kargoID).to.equal('test-lexid-cerberus-localstorage'); + expect(payload.user.clientID).to.equal('test-clientid-cerberus-localstorage'); + expect(payload.user.optOut).to.equal(false); + }); - function simulateNoCurrencyObject() { - undefinedCurrency = true; - noAdServerCurrency = false; - nonUSDAdServerCurrency = false; - } + it('retrieves CRB from cookies if localstorage is missing', function() { + setCrb('valid', false); - function simulateNoAdServerCurrency() { - undefinedCurrency = false; - noAdServerCurrency = true; - nonUSDAdServerCurrency = false; - } + const payload = getPayloadFromTestBids(testBids, bidderRequest); - function simulateNonUSDAdServerCurrency() { - undefinedCurrency = false; - noAdServerCurrency = false; - nonUSDAdServerCurrency = true; - } + expect(payload.rawCRB).to.equal(crbValues.valid); + expect(payload.rawCRBLocalStorage).to.be.undefined; + expect(payload.user.crbIDs).to.deep.equal(validCrbIds); + expect(payload.user.tdID).to.equal('test-tdid-cerberus-cookie'); + expect(payload.user.kargoID).to.equal('test-lexid-cerberus-cookie'); + expect(payload.user.clientID).to.equal('test-clientid-cerberus-cookie'); + expect(payload.user.optOut).to.equal(false); + }); - function generateGDPR(applies, haveConsent) { - var data = { - consentString: 'gdprconsentstring', - gdprApplies: applies, - }; - return data; - } + it('retrieves CRB from cookies if localstorage is not functional', function() { + // Note: this does not cause localStorage to throw an error in Firefox so in that browser this + // test is not 100% true to its name + sandbox.stub(localStorage, 'getItem').throws(); + setCrb('valid', 'invalid'); + + const payload = getPayloadFromTestBids(testBids, bidderRequest); + + expect(payload.rawCRB).to.equal(crbValues.valid); + expect(payload.rawCRBLocalStorage).to.be.undefined; + expect(payload.user.crbIDs).to.deep.equal(validCrbIds); + expect(payload.user.tdID).to.equal('test-tdid-cerberus-cookie'); + expect(payload.user.kargoID).to.equal('test-lexid-cerberus-cookie'); + expect(payload.user.clientID).to.equal('test-clientid-cerberus-cookie'); + expect(payload.user.optOut).to.equal(false); + }); - function generateGDPRExpect(applies, haveConsent) { - return { - consent: 'gdprconsentstring', - applies: applies, - }; - } + it('does not fail if CRB is missing', function() { + const payload = getPayloadFromTestBids(testBids, bidderRequest); - function generatePageView() { - return { - id: '112233', - timestamp: frozenNow.getTime(), - url: 'http://pageview.url' - } - } + expect(payload.rawCRB).to.be.undefined; + expect(payload.rawCRBLocalStorage).to.be.undefined; + expect(payload.user.crbIDs).to.deep.equal({}); + expect(payload.user.tdID).to.be.undefined; + expect(payload.user.kargoID).to.be.undefined; + expect(payload.user.clientID).to.be.undefined; + expect(payload.user.optOut).to.be.undefined; + }); - function generateRawCRB(rawCRB, rawCRBLocalStorage) { - if (rawCRB == null && rawCRBLocalStorage == null) { - return null - } + it('fails gracefully if the CRB is invalid base 64 cookie', function() { + setCrb('invalidB64', false); - let result = {} + const payload = getPayloadFromTestBids(testBids, bidderRequest); - if (rawCRB != null) { - result.rawCRB = rawCRB - } + expect(payload.rawCRB).to.equal(crbValues.invalidB64); + expect(payload.rawCRBLocalStorage).to.be.undefined; + expect(payload.user.crbIDs).to.deep.equal({}); + expect(payload.user.tdID).to.be.undefined; + expect(payload.user.kargoID).to.be.undefined; + expect(payload.user.clientID).to.be.undefined; + expect(payload.user.optOut).to.be.undefined; + }); - if (rawCRBLocalStorage != null) { - result.rawCRBLocalStorage = rawCRBLocalStorage - } + it('fails gracefully if the CRB is invalid base 64 localStorage', function() { + setCrb(false, 'invalidB64'); - return result - } + const payload = getPayloadFromTestBids(testBids, bidderRequest); - function getKrgCrb() { - return 'eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwibGV4SWQiOiI1ZjEwODgzMS0zMDJkLTExZTctYmY2Yi00NTk1YWNkM2JmNmMiLCJjbGllbnRJZCI6IjI0MTBkOGYyLWMxMTEtNDgxMS04OGE1LTdiNWUxOTBlNDc1ZiIsIm9wdE91dCI6ZmFsc2UsImV4cGlyZVRpbWUiOjE0OTc0NDkzODI2NjgsImxhc3RTeW5jZWRBdCI6MTQ5NzM2Mjk3OTAxMn0='; - } + expect(payload.rawCRB).to.be.undefined; + expect(payload.rawCRBLocalStorage).to.equal(crbValues.invalidB64Ls); + expect(payload.user.crbIDs).to.deep.equal({}); + expect(payload.user.tdID).to.be.undefined; + expect(payload.user.kargoID).to.be.undefined; + expect(payload.user.clientID).to.be.undefined; + expect(payload.user.optOut).to.be.undefined; + }); - function getKrgCrbOldStyle() { - return '{"v":"eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwibGV4SWQiOiI1ZjEwODgzMS0zMDJkLTExZTctYmY2Yi00NTk1YWNkM2JmNmMiLCJjbGllbnRJZCI6IjI0MTBkOGYyLWMxMTEtNDgxMS04OGE1LTdiNWUxOTBlNDc1ZiIsIm9wdE91dCI6ZmFsc2UsImV4cGlyZVRpbWUiOjE0OTc0NDkzODI2NjgsImxhc3RTeW5jZWRBdCI6MTQ5NzM2Mjk3OTAxMn0="}'; - } + [ + [ 'valid', 'invalidB64', 'cookie' ], + [ 'valid', 'invalidJson', 'cookie' ], + [ 'invalidB64', 'invalidJson', 'none' ], + [ 'invalidB64', 'invalidB64', 'none' ], + [ 'invalidB64', 'valid', 'localStorage' ], + [ 'invalidJson', 'invalidJson', 'none' ], + [ 'invalidJson', 'invalidB64', 'none' ], + [ 'invalidJson', 'valid', 'localStorage' ], + ].forEach(config => { + it(`uses ${config[2]} if the cookie is ${config[0]} and localStorage is ${config[1]}`, function() { + setCrb(config[0], config[1]); + const payload = getPayloadFromTestBids(testBids, bidderRequest); + + expect(payload.rawCRB).to.equal(crbValues[config[0]]); + expect(payload.rawCRBLocalStorage).to.equal(crbValues[`${config[1]}Ls`]); + if (config[2] === 'cookie') { + expect(payload.user.crbIDs).to.deep.equal(validCrbIds); + expect(payload.user.tdID).to.equal('test-tdid-cerberus-cookie'); + expect(payload.user.kargoID).to.equal('test-lexid-cerberus-cookie'); + expect(payload.user.clientID).to.equal('test-clientid-cerberus-cookie'); + expect(payload.user.optOut).to.equal(false); + } else if (config[2] === 'localStorage') { + expect(payload.user.crbIDs).to.deep.equal(validCrbIdsLs); + expect(payload.user.tdID).to.equal('test-tdid-cerberus-localstorage'); + expect(payload.user.kargoID).to.equal('test-lexid-cerberus-localstorage'); + expect(payload.user.clientID).to.equal('test-clientid-cerberus-localstorage'); + expect(payload.user.optOut).to.equal(false); + } else { + expect(payload.user.crbIDs).to.deep.equal({}); + expect(payload.user.tdID).to.be.undefined; + expect(payload.user.kargoID).to.be.undefined; + expect(payload.user.clientID).to.be.undefined; + expect(payload.user.optOut).to.be.undefined; + } + }); + }); - function initializeKrgCrb(cookieOnly) { - if (!cookieOnly) { - setLocalStorageItem('krg_crb', getKrgCrb()); - } - setCookie('krg_crb', getKrgCrbOldStyle()); - } + it('uses the tdID from cerberus to populate the tdID field', function() { + setCrb('valid', 'valid'); + const payload = getPayloadFromTestBids(testBids, bidderRequest); - function getInvalidKrgCrbType1() { - return 'invalid-krg-crb'; - } + expect(payload.user.tdID).to.equal('test-tdid-cerberus-localstorage'); + }); - function initializeInvalidKrgCrbType1() { - setLocalStorageItem('krg_crb', getInvalidKrgCrbType1()); - } + it('uses the lexId from cerberus to populate the kargoID field', function() { + setCrb('valid', 'valid'); + const payload = getPayloadFromTestBids(testBids, bidderRequest); - function initializeInvalidKrgCrbType1Cookie() { - setCookie('krg_crb', getInvalidKrgCrbType1()); - } + expect(payload.user.kargoID).to.equal('test-lexid-cerberus-localstorage'); + }); - function getInvalidKrgCrbType2() { - return 'Ly8v'; - } + it('uses the clientId from cerberus to populate the clientID field', function() { + setCrb('valid', 'valid'); + const payload = getPayloadFromTestBids(testBids, bidderRequest); - function getInvalidKrgCrbType2OldStyle() { - return '{"v":"&&&&&&"}'; - } + expect(payload.user.clientID).to.equal('test-clientid-cerberus-localstorage'); + }); - function initializeInvalidKrgCrbType2() { - setLocalStorageItem('krg_crb', getInvalidKrgCrbType2()); - } + it('uses the optOut from cerberus to populate the clientID field', function() { + setCrb('valid', 'valid'); + let payload; + payload = getPayloadFromTestBids(testBids, bidderRequest); - function initializeInvalidKrgCrbType2Cookie() { - setCookie('krg_crb', getInvalidKrgCrbType2OldStyle()); - } + expect(payload.user.optOut).to.equal(false); - function getInvalidKrgCrbType3OldStyle() { - return '{"v":"Ly8v"}'; - } + setLocalStorageValue('krg_crb', buildCrbValue(false, true, true, true, true, true)); + payload = getPayloadFromTestBids(testBids, bidderRequest); - function initializeInvalidKrgCrbType3Cookie() { - setCookie('krg_crb', getInvalidKrgCrbType3OldStyle()); - } + expect(payload.user.optOut).to.equal(true); + }); + }); - function getInvalidKrgCrbType4OldStyle() { - return '{"v":"bnVsbA=="}'; - } + describe('user', function() { + it('fetches the trade desk id from the adapter if present', function() { + const payload = getPayloadFromTestBids([{ + ...minimumBidParams, + userId: { + tdid: 'test-tdid-module' + } + }]); - function initializeInvalidKrgCrbType4Cookie() { - setCookie('krg_crb', getInvalidKrgCrbType4OldStyle()); - } + expect(payload.user.tdID).to.equal('test-tdid-module'); + }); - function getEmptyKrgCrb() { - return 'eyJleHBpcmVUaW1lIjoxNDk3NDQ5MzgyNjY4LCJsYXN0U3luY2VkQXQiOjE0OTczNjI5NzkwMTJ9'; - } + it('fetches the trade desk id from cerberus if present', function() { + setLocalStorageValue('krg_crb', btoa(JSON.stringify({ tdID: 'test-tdid-crb' }))); - function getEmptyKrgCrbOldStyle() { - return '{"v":"eyJleHBpcmVUaW1lIjoxNDk3NDQ5MzgyNjY4LCJsYXN0U3luY2VkQXQiOjE0OTczNjI5NzkwMTJ9"}'; - } + const payload = getPayloadFromTestBids([{ + ...minimumBidParams, + }]); - function initializeEmptyKrgCrb() { - setLocalStorageItem('krg_crb', getEmptyKrgCrb()); - } + expect(payload.user.tdID).to.equal('test-tdid-crb'); + }); - function initializePageView() { - setLocalStorageItem('pageViewId', 112233); - setLocalStorageItem('pageViewTimestamp', frozenNow.getTime()); - setLocalStorageItem('pageViewUrl', 'http://pageview.url'); - } + it('fetches the trade desk id from the adapter if adapter and cerberus are present', function() { + setLocalStorageValue('krg_crb', buildCrbValue(false, true, true, true, true, false)); - function initializeEmptyKrgCrbCookie() { - setCookie('krg_crb', getEmptyKrgCrbOldStyle()); - } + const payload = getPayloadFromTestBids([{ + ...minimumBidParams, + userId: { + tdid: 'test-tdid-module' + } + }]); + + expect(payload.user.tdID).to.equal('test-tdid-module'); + }); + + it('does not set kargoId if it is not present', function() { + const payload = getPayloadFromTestBids([{ ...minimumBidParams }]); + + expect(payload.user.lexId).to.be.undefined; + }); + + it('does not populate usp, gdpr, or gpp if they are not present', function() { + const payload = getPayloadFromTestBids([{ ...minimumBidParams }]); + + expect(payload.user.usp).to.be.undefined; + expect(payload.user.gdpr).to.be.undefined; + expect(payload.user.gpp).to.be.undefined; + }); + + it('fetches usp from the bidder request if present', function() { + bidderRequest.uspConsent = '1---'; + const payload = getPayloadFromTestBids([{ ...minimumBidParams }]); + + expect(payload.user.usp).to.equal('1---'); + }); + + it('fetches gpp from the bidder request if present', function() { + bidderRequest.gppConsent = { + consentString: 'gppString', + applicableSections: [-1] + }; + const payload = getPayloadFromTestBids([{ ...minimumBidParams }]); + + expect(payload.user.gpp).to.deep.equal({ + gppString: 'gppString', + applicableSections: [-1] + }); + }); + + it('does not send empty gpp values', function() { + bidderRequest.gppConsent = { + consentString: '', + applicableSections: '' + }; + const payload = getPayloadFromTestBids([{ ...minimumBidParams }]); + + expect(payload.user.gpp).to.be.undefined; + }); + + it('fetches gdpr consent from the bidder request if present', function() { + bidderRequest.gdprConsent = { + consentString: 'gdpr-consent-string', + gdprApplies: true + }; + const payload = getPayloadFromTestBids([{ ...minimumBidParams }]); + + expect(payload.user.gdpr).to.deep.equal({ + consent: 'gdpr-consent-string', + applies: true + }); + }); + + it('handles malformed gdpr applies from the bidder request', function() { + [ + ['true', true], + ['false', true], + ['1', true], + [1, true], + [0, false], + ['0', true], + ['y', true], + ['yes', true], + ['n', true], + ['no', true], + [null, false], + [{}, true], + ].forEach(testValue => { + bidderRequest.gdprConsent = { gdprApplies: testValue[0] }; + const payload = getPayloadFromTestBids([{ ...minimumBidParams }]); + expect(payload.user.gdpr, `Value - ${JSON.stringify(testValue[0])}`).to.deep.equal({ + consent: '', + applies: testValue[1] + }); + }); + }); + + it('passes the user.data from the first bid request if availabale', function() { + let payload; + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + }, { + ...minimumBidParams, + ortb2: { user: { data: { test: 'value' } } } + }]); + expect(payload.user.data).to.deep.equal([]); + + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + ortb2: { user: { data: { test: 'value' } } } + }, { + ...minimumBidParams, + ortb2: { user: { data: { test2: 'value2' } } } + }]); + expect(payload.user.data).to.deep.equal({ + test: 'value' + }); + }); + + it('fails gracefully if there is no localStorage', function() { + sandbox.stub(localStorage, 'getItem').throws(); + let payload = getPayloadFromTestBids(testBids); + expect(payload.user).to.deep.equal({ + crbIDs: {}, + data: [] + }); + }); + }); + + describe('sua', function() { + it('is not provided if not present in the first valid bid', function() { + let payload = getPayloadFromTestBids([ + ...testBids, + { + ...minimumBidParams, + ortb2: { device: { sua: { + platform: { + brand: 'macOS', + version: ['12', '6', '0'] + }, + browsers: [ + { + brand: 'Chromium', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Google Chrome', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Not;A=Brand', + version: ['99', '0', '0', '0'] + } + ], + mobile: 1, + model: 'model', + source: 1, + } } } + } + ]); + expect(payload.device.sua).to.be.undefined; + }); + + it('is provided if present in the first valid bid', function() { + let payload = getPayloadFromTestBids([ + { + ...minimumBidParams, + ortb2: { device: { sua: { + platform: { + brand: 'macOS', + version: ['12', '6', '0'] + }, + browsers: [ + { + brand: 'Chromium', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Google Chrome', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Not;A=Brand', + version: ['99', '0', '0', '0'] + } + ], + mobile: 1, + model: 'model', + source: 1, + } } } + }, + { + ...minimumBidParams, + ortb2: { device: { sua: { + platform: { + brand: 'macOS2', + version: ['122', '6', '0'] + }, + browsers: [ + { + brand: 'Chromium2', + version: ['1062', '0', '5249', '119'] + }, + { + brand: 'Google Chrome2', + version: ['102', '0', '5249', '119'] + }, + { + brand: 'Not;A=Brand2', + version: ['992', '0', '0', '0'] + } + ], + mobile: 2, + model: 'model2', + source: 2, + } } } + } + ]); + expect(payload.device.sua).to.deep.equal({ + platform: { + brand: 'macOS', + version: ['12', '6', '0'] + }, + browsers: [ + { + brand: 'Chromium', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Google Chrome', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Not;A=Brand', + version: ['99', '0', '0', '0'] + } + ], + mobile: 1, + model: 'model', + source: 1, + }); + }); + + it('does not send non-mapped attributes', function() { + let payload = getPayloadFromTestBids([{...minimumBidParams, + ortb2: { device: { sua: { + other: 'value', + objectMissing: { + key: 'value' + }, + platform: { + brand: 'macOS', + version: ['12', '6', '0'] + }, + browsers: [ + { + brand: 'Chromium', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Google Chrome', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Not;A=Brand', + version: ['99', '0', '0', '0'] + } + ], + mobile: 1, + model: 'model', + source: 1, + } } } + }]); + expect(payload.device.sua).to.deep.equal({ + platform: { + brand: 'macOS', + version: ['12', '6', '0'] + }, + browsers: [ + { + brand: 'Chromium', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Google Chrome', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Not;A=Brand', + version: ['99', '0', '0', '0'] + } + ], + mobile: 1, + model: 'model', + source: 1, + }); + }); - function getSessionId() { - return spec._getSessionId(); - } + it('does not send non-truthy values or empty strings', function() { + [ + false, + 0, + null, + '', + ' ', + ' ', + ].forEach(value => { + let payload = getPayloadFromTestBids([{...minimumBidParams, + ortb2: { device: { sua: { + platform: value, + browsers: [ + { + brand: 'Chromium', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Google Chrome', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Not;A=Brand', + version: ['99', '0', '0', '0'] + } + ], + mobile: 1, + model: 'model', + source: 1, + } } } + }]); + expect(payload.device.sua, `Value - ${JSON.stringify(value)}`).to.deep.equal({ + browsers: [ + { + brand: 'Chromium', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Google Chrome', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Not;A=Brand', + version: ['99', '0', '0', '0'] + } + ], + mobile: 1, + model: 'model', + source: 1, + }); + }); + }); - function getExpectedKrakenParams(expectedCRB, expectedPage, excludeUserIds, expectedGDPR, currency) { - var base = { - pbv: '$prebid.version$', - aid: '1234098', - requestCount: 0, - sid: getSessionId(), - url: 'https://www.prebid.org', - timeout: 200, - ts: frozenNow.getTime(), - schain: testSchain, - device: { - size: [ - screen.width, - screen.height - ], - sua: { + it('does not send 0 for mobile or source', function() { + let payload = getPayloadFromTestBids([{ + ...minimumBidParams, + ortb2: { device: { sua: { platform: { brand: 'macOS', version: ['12', '6', '0'] @@ -474,506 +1498,217 @@ describe('kargo adapter tests', function () { version: ['99', '0', '0', '0'] } ], - mobile: 1, + mobile: 0, model: 'model', - source: 1 - }, - }, - site: { - cat: ['IAB1', 'IAB2', 'IAB3'] - }, - imp: [ - { - code: '101', - id: '1', - pid: 'foo', - tid: '10101', - banner: { - sizes: [[320, 50], [300, 50]] - }, - bidRequestCount: 1, - bidderRequestCount: 2, - bidderWinCount: 3, - floor: 1, - fpd: { - gpid: '/22558409563,18834096/dfy_mobile_adhesion' - }, - ext: { - ortb2Imp: { - ext: { - tid: '10101', - data: { - adServer: { - name: 'gam', - adslot: '/22558409563,18834096/dfy_mobile_adhesion' - }, - pbadslot: '/22558409563,18834096/dfy_mobile_adhesion' - }, - gpid: '/22558409563,18834096/dfy_mobile_adhesion' - } - } - } - }, - { - code: '202', - id: '2', - pid: 'bar', - tid: '20202', - video: { - sizes: [[320, 50], [300, 50]] - }, - fpd: { - gpid: '/22558409563,18834096/dfy_mobile_adhesion' - }, - floor: 2, - ext: { - ortb2Imp: { - ext: { - tid: '20202', - data: { - adServer: { - name: 'gam', - adslot: '/22558409563,18834096/dfy_mobile_adhesion' - }, - pbadslot: '/22558409563,18834096/dfy_mobile_adhesion' - } - } - } - } - }, - { - code: '303', - id: '3', - pid: 'bar', - tid: '30303', - native: { - sizes: [[320, 50], [300, 50]] - }, - fpd: { - gpid: '/22558409563,18834096/dfy_mobile_adhesion' - }, - floor: 3, - ext: { - ortb2Imp: { - ext: { - tid: '30303', - data: { - adServer: { - name: 'gam', - adslot: '/22558409563,18834096/dfy_mobile_adhesion' - } - }, - gpid: '/22558409563,18834096/dfy_mobile_adhesion' - } - } - } - } - ], - socan: { - segments: ['segment_1', 'segment_2', 'segment_3'], - url: 'https://socan.url' - }, - user: { - kargoID: '5f108831-302d-11e7-bf6b-4595acd3bf6c', - clientID: '2410d8f2-c111-4811-88a5-7b5e190e475f', - tdID: 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c', - crbIDs: { - 2: '82fa2555-5969-4614-b4ce-4dcf1080e9f9', - 16: 'VoxIk8AoJz0AAEdCeyAAAAC2&502', - 23: 'd2a855a5-1b1c-4300-940e-a708fa1f1bde', - 24: 'VoxIk8AoJz0AAEdCeyAAAAC2&502', - 25: '5ee24138-5e03-4b9d-a953-38e833f2849f', - '2_80': 'd2a855a5-1b1c-4300-940e-a708fa1f1bde', - '2_93': '5ee24138-5e03-4b9d-a953-38e833f2849f' + source: 0, + } } } + }]); + expect(payload.device.sua).to.deep.equal({ + platform: { + brand: 'macOS', + version: ['12', '6', '0'] }, - optOut: false, - usp: '1---', - sharedIDEids: [ + browsers: [ { - source: 'adserver.org', - uids: [ - { - id: 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c', - atype: 1, - ext: { - rtiPartner: 'TDID' - } - } - ] + brand: 'Chromium', + version: ['106', '0', '5249', '119'] }, { - source: 'adquery.io', - uids: [ - { - id: 'adqueryId-123', - atype: 1 - } - ] + brand: 'Google Chrome', + version: ['106', '0', '5249', '119'] }, { - source: 'criteo.com', - uids: [ - { - id: 'criteoId-456', - atype: 1 - } - ] + brand: 'Not;A=Brand', + version: ['99', '0', '0', '0'] } ], - data: [ - { - name: 'prebid.org', - ext: { - segtax: 600, - segclass: 'v1', - }, - segment: [ - { - id: '133' - } - ] - } - ] - }, - ext: { - ortb2: { - device: { - sua: { - platform: { - brand: 'macOS', - version: ['12', '6', '0'] - }, - browsers: [ - { - brand: 'Chromium', - version: ['106', '0', '5249', '119'] - }, - { - brand: 'Google Chrome', - version: ['106', '0', '5249', '119'] - }, - { - brand: 'Not;A=Brand', - version: ['99', '0', '0', '0'] - } - ], - mobile: 1, - model: 'model', - source: 1, - } - }, - site: { - id: '1234', - name: 'SiteName', - cat: ['IAB1', 'IAB2', 'IAB3'] - }, - user: { - data: [ - { - name: 'prebid.org', - ext: { - segtax: 600, - segclass: 'v1', - }, - segment: [ - { - id: '133' - }, - ] - }, - ] - } - } - } - }; - - if (excludeUserIds) { - base.user.crbIDs = {}; - delete base.user.clientID; - delete base.user.kargoID; - delete base.user.optOut; - } - - if (expectedGDPR) { - base.user.gdpr = expectedGDPR; - } - - if (expectedPage) { - base.page = expectedPage; - } + model: 'model', + }); + }); + }); - if (currency) { - base.cur = currency; - } + describe('page', function() { + it('pulls the page ID from localStorage', function() { + setLocalStorageValue('pageViewId', 'test-page-id'); + let payload = getPayloadFromTestBids(testBids); + expect(payload.page).to.deep.equal({ + id: 'test-page-id' + }); + }); - const reqCount = requestCount++; - base.requestCount = reqCount + it('pulls the page timestamp from localStorage', function() { + setLocalStorageValue('pageViewTimestamp', '123456789'); + let payload = getPayloadFromTestBids(testBids); + expect(payload.page).to.deep.equal({ + timestamp: 123456789 + }); + }); - if (expectedCRB != null) { - if (expectedCRB.rawCRB != null) { - base.rawCRB = expectedCRB.rawCRB - } - if (expectedCRB.rawCRBLocalStorage != null) { - base.rawCRBLocalStorage = expectedCRB.rawCRBLocalStorage - } - } + it('pulls the page ID from localStorage', function() { + setLocalStorageValue('pageViewUrl', 'https://test-url.com'); + let payload = getPayloadFromTestBids(testBids); + expect(payload.page).to.deep.equal({ + url: 'https://test-url.com' + }); + }); - return base; - } + it('pulls all 3 together', function() { + setLocalStorageValue('pageViewId', 'test-page-id'); + setLocalStorageValue('pageViewTimestamp', '123456789'); + setLocalStorageValue('pageViewUrl', 'https://test-url.com'); + let payload = getPayloadFromTestBids(testBids); + expect(payload.page).to.deep.equal({ + id: 'test-page-id', + timestamp: 123456789, + url: 'https://test-url.com' + }); + }); - function testBuildRequests(expected, gdpr) { - var clonedBids = JSON.parse(JSON.stringify(bids)); + it('fails gracefully without localStorage', function() { + sandbox.stub(localStorage, 'getItem').throws(); + let payload = getPayloadFromTestBids(testBids); + expect(payload.page).to.be.undefined; + }); + }); + }); - var payload = { - timeout: 200, - uspConsent: '1---', - refererInfo: { - page: 'https://www.prebid.org', + describe('interpretResponse', function() { + const response = Object.freeze({ body: { + 1: { + id: 'foo', + cpm: 3, + adm: '
', + width: 320, + height: 50, + metadata: {}, + creativeID: 'bar' + }, + 2: { + id: 'bar', + cpm: 2.5, + adm: '
', + width: 300, + height: 250, + targetingCustom: 'dmpmptest1234', + metadata: { + landingPageDomain: ['https://foobar.com'] }, - }; - - if (gdpr) { - payload['gdprConsent'] = gdpr + creativeID: 'foo' + }, + 3: { + id: 'bar', + cpm: 2.5, + adm: '
', + width: 300, + height: 250, + creativeID: 'foo' + }, + 4: { + id: 'bar', + cpm: 2.5, + adm: '
', + width: 300, + height: 250, + mediaType: 'banner', + metadata: {}, + creativeID: 'foo', + currency: 'EUR' + }, + 5: { + id: 'bar', + cpm: 2.5, + adm: '', + width: 300, + height: 250, + mediaType: 'video', + metadata: {}, + creativeID: 'foo', + currency: 'EUR' + }, + 6: { + id: 'bar', + cpm: 2.5, + adm: '', + admUrl: 'https://foobar.com/vast_adm', + width: 300, + height: 250, + mediaType: 'video', + metadata: {}, + creativeID: 'foo', + currency: 'EUR' } - - clonedBids.forEach(bid => { - if (bid.mediaTypes.banner) { - bid.getFloor = () => ({ currency: 'USD', floor: 1 }); - } else if (bid.mediaTypes.video) { - bid.getFloor = () => ({ currency: 'USD', floor: 2 }); - } else if (bid.mediaTypes.native) { - bid.getFloor = () => ({ currency: 'USD', floor: 3 }); + }}); + const bidderRequest = Object.freeze({ + currency: 'USD', + bids: [{ + bidId: 1, + params: { + placementId: 'foo' } - }); - - var request = spec.buildRequests(clonedBids, payload); - var krakenParams = request.data; - - expect(request.url).to.equal('https://krk2.kargo.com/api/v1/prebid'); - expect(request.method).to.equal('POST'); - expect(request.timeout).to.equal(200); - expect(krakenParams).to.deep.equal(expected); - - // Make sure session ID stays the same across requests simulating multiple auctions on one page load - for (let i in sessionIds) { - if (i == 0) { - continue; + }, { + bidId: 2, + params: { + placementId: 'bar' } - let sessionId = sessionIds[i]; - expect(sessionIds[0]).to.equal(sessionId); - } - } - - it('works when all params and localstorage and cookies are correctly set', function () { - initializeKrgCrb(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView())); - }); - - it('works when all params and cookies are correctly set but no localstorage', function () { - initializeKrgCrb(true); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle()))); - }); - - it('gracefully handles nothing being set', function () { - testBuildRequests(getExpectedKrakenParams(undefined, undefined, true)); - }); - - it('gracefully handles browsers without localStorage', function () { - simulateNoLocalStorage(); - testBuildRequests(getExpectedKrakenParams(undefined, undefined, true)); - }); - - it('handles empty yet valid Kargo CRB', function () { - initializeEmptyKrgCrb(); - initializeEmptyKrgCrbCookie(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getEmptyKrgCrbOldStyle(), getEmptyKrgCrb()), generatePageView(), true)); - }); - - it('handles broken Kargo CRBs where base64 encoding is invalid', function () { - initializeInvalidKrgCrbType1(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(undefined, getInvalidKrgCrbType1()), generatePageView(), true)); - }); - - it('handles broken Kargo CRBs where top level JSON is invalid on cookie', function () { - initializeInvalidKrgCrbType1Cookie(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType1()), generatePageView(), true)); - }); - - it('handles broken Kargo CRBs where decoded JSON is invalid', function () { - initializeInvalidKrgCrbType2(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(undefined, getInvalidKrgCrbType2()), generatePageView(), true)); - }); - - it('handles broken Kargo CRBs where inner base 64 is invalid on cookie', function () { - initializeInvalidKrgCrbType2Cookie(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType2OldStyle()), generatePageView(), true)); - }); - - it('handles broken Kargo CRBs where inner JSON is invalid on cookie', function () { - initializeInvalidKrgCrbType3Cookie(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType3OldStyle()), generatePageView(), true)); - }); - - it('handles broken Kargo CRBs where inner JSON is falsey', function () { - initializeInvalidKrgCrbType4Cookie(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType4OldStyle()), generatePageView(), true)); - }); - - it('handles a non-existant currency object on the config', function () { - simulateNoCurrencyObject(); - initializeKrgCrb(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView())); - }); - - it('handles no ad server currency being set on the currency object in the config', function () { - simulateNoAdServerCurrency(); - initializeKrgCrb(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView())); - }); - - it('handles non-USD ad server currency being set on the currency object in the config', function () { - simulateNonUSDAdServerCurrency(); - initializeKrgCrb(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView(), undefined, undefined, 'EUR')); + }, { + bidId: 3, + params: { + placementId: 'bar' + } + }, { + bidId: 4, + params: { + placementId: 'bar' + } + }, { + bidId: 5, + params: { + placementId: 'bar' + } + }, { + bidId: 6, + params: { + placementId: 'bar' + } + }] }); - it('sends gdpr consent', function () { - initializeKrgCrb(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), undefined, false, generateGDPRExpect(true, true)), generateGDPR(true, true)); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), undefined, false, generateGDPRExpect(false, true)), generateGDPR(false, true)); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), undefined, false, generateGDPRExpect(false, false)), generateGDPR(false, false)); + it('returns an empty array if the response body is empty or not an object', function() { + [ + '', + undefined, + false, + true, + null, + [], + {}, + 1234, + ].forEach(value => { + let bids = spec.interpretResponse({ body: value }, bidderRequest); + expect(bids, `Value - ${JSON.stringify(value)}`).to.deep.equal([]); + }); }); - }); - describe('response handler', function () { - it('handles bid responses', function () { - var resp = spec.interpretResponse({ - body: { - 1: { - id: 'foo', - cpm: 3, - adm: '
', - width: 320, - height: 50, - metadata: {}, - creativeID: 'bar' - }, - 2: { - id: 'bar', - cpm: 2.5, - adm: '
', - width: 300, - height: 250, - targetingCustom: 'dmpmptest1234', - metadata: { - landingPageDomain: ['https://foobar.com'] - }, - creativeID: 'foo' - }, - 3: { - id: 'bar', - cpm: 2.5, - adm: '
', - width: 300, - height: 250, - creativeID: 'foo' - }, - 4: { - id: 'bar', - cpm: 2.5, - adm: '
', - width: 300, - height: 250, - mediaType: 'banner', - metadata: {}, - creativeID: 'foo', - currency: 'EUR' - }, - 5: { - id: 'bar', - cpm: 2.5, - adm: '', - width: 300, - height: 250, - mediaType: 'video', - metadata: {}, - creativeID: 'foo', - currency: 'EUR' - }, - 6: { - id: 'bar', - cpm: 2.5, - adm: '', - admUrl: 'https://foobar.com/vast_adm', - width: 300, - height: 250, - mediaType: 'video', - metadata: {}, - creativeID: 'foo', - currency: 'EUR' - } - } - }, { - currency: 'USD', - bids: [{ - bidId: 1, - params: { - placementId: 'foo' - } - }, { - bidId: 2, - params: { - placementId: 'bar' - } - }, { - bidId: 3, - params: { - placementId: 'bar' - } - }, { - bidId: 4, - params: { - placementId: 'bar' - } - }, { - bidId: 5, - params: { - placementId: 'bar' - } - }, { - bidId: 6, - params: { - placementId: 'bar' - } - }] - }); - var expectation = [{ + it('returns bid response for various objects', function() { + let bids = spec.interpretResponse(response, bidderRequest); + expect(bids).to.have.length(Object.keys(response.body).length); + expect(bids[0]).to.deep.equal({ ad: '
', - requestId: '1', cpm: 3, - width: 320, - height: 50, - ttl: 300, creativeId: 'bar', - dealId: undefined, - netRevenue: true, currency: 'USD', + dealId: undefined, + height: 50, mediaType: 'banner', meta: { mediaType: 'banner' - } - }, { + }, + netRevenue: true, + requestId: '1', + ttl: 300, + width: 320, + }); + expect(bids[1]).to.deep.equal({ requestId: '2', ad: '
', cpm: 2.5, @@ -990,7 +1725,8 @@ describe('kargo adapter tests', function () { clickUrl: 'https://foobar.com', advertiserDomains: ['https://foobar.com'] } - }, { + }); + expect(bids[2]).to.deep.equal({ requestId: '3', ad: '
', cpm: 2.5, @@ -1005,7 +1741,8 @@ describe('kargo adapter tests', function () { meta: { mediaType: 'banner' } - }, { + }); + expect(bids[3]).to.deep.equal({ requestId: '4', ad: '
', cpm: 2.5, @@ -1020,7 +1757,8 @@ describe('kargo adapter tests', function () { meta: { mediaType: 'banner' } - }, { + }); + expect(bids[4]).to.deep.equal({ requestId: '5', cpm: 2.5, width: 300, @@ -1035,7 +1773,8 @@ describe('kargo adapter tests', function () { meta: { mediaType: 'video' } - }, { + }); + expect(bids[5]).to.deep.equal({ requestId: '6', cpm: 2.5, width: 300, @@ -1050,148 +1789,200 @@ describe('kargo adapter tests', function () { meta: { mediaType: 'video' } - }]; - expect(resp).to.deep.equal(expectation); + }); }); - }); - - describe('user sync handler', function () { - const clientId = '74c81cbb-7d07-46d9-be9b-68ccb291c949'; - var shouldSimulateOutdatedBrowser, crb, isActuallyOutdatedBrowser; - beforeEach(() => { - $$PREBID_GLOBAL$$.bidderSettings = { - kargo: { - storageAllowed: true + it('adds landingPageDomain data', function() { + let response = spec.interpretResponse({ body: { 0: { + metadata: { + landingPageDomain: [ + 'https://foo.com', + 'https://bar.com' + ] } - }; - crb = {}; - shouldSimulateOutdatedBrowser = false; - isActuallyOutdatedBrowser = false; - - // IE11 fails these tests in the Prebid test suite. Since this - // browser won't support any of this stuff we expect all user - // syncing to fail gracefully. Kargo is mobile only, so this - // doesn't really matter. - if (!window.crypto) { - isActuallyOutdatedBrowser = true; - } else { - sandbox.stub(crypto, 'getRandomValues').callsFake(function (buf) { - if (shouldSimulateOutdatedBrowser) { - throw new Error('Could not generate random values'); - } - var bytes = [50, 5, 232, 133, 141, 55, 49, 57, 244, 126, 248, 44, 255, 38, 128, 0]; - for (var i = 0; i < bytes.length; i++) { - buf[i] = bytes[i]; - } - return buf; - }); - } - - sandbox.stub(spec, '_getCrb').callsFake(function () { - return crb; + } } }, {}); + expect(response[0].meta).to.deep.equal({ + mediaType: 'banner', + clickUrl: 'https://foo.com', + advertiserDomains: [ 'https://foo.com', 'https://bar.com' ] }); }); + }); - function getUserSyncsWhenAllowed(gdprConsent, usPrivacy, gppConsent) { - return spec.getUserSyncs({ iframeEnabled: true }, null, gdprConsent, usPrivacy, gppConsent); - } + describe('getUserSyncs', function() { + let crb = {}; + const clientId = 'random-client-id-string'; + const baseUrl = 'https://crb.kargo.com/api/v1/initsyncrnd/random-client-id-string?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=0&gdpr=0&gdpr_consent=&us_privacy=&gpp=&gpp_sid='; + + function buildSyncUrls(baseUrl = 'https://crb.kargo.com/api/v1/initsyncrnd/random-client-id-string?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=0&gdpr=0&gdpr_consent=&us_privacy=&gpp=&gpp_sid=') { + let syncs = []; + for (let i = 0; i < 5; i++) { + syncs.push({ + type: 'iframe', + url: baseUrl.replace(/idx=\d+&/, `idx=${i}&`), + }); + } - function getUserSyncsWhenForbidden() { - return spec.getUserSyncs({}); + return syncs; } - function turnOnClientId() { - crb.clientId = clientId; + function getUserSyncs(gdpr, usp, gpp) { + return spec.getUserSyncs( + { iframeEnabled: true }, + null, + gdpr, + usp, + gpp + ); } - function simulateOutdatedBrowser() { - shouldSimulateOutdatedBrowser = true; - } + beforeEach(function() { + crb = { clientId }; + sandbox.stub(spec, '_getCrb').callsFake(function() { return crb; }); - function getSyncUrl(index, gdprApplies, gdprConsentString, usPrivacy, gpp, gppSid) { - return { - type: 'iframe', - url: `https://crb.kargo.com/api/v1/initsyncrnd/${clientId}?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=${index}&gdpr=${gdprApplies}&gdpr_consent=${gdprConsentString}&us_privacy=${usPrivacy}&gpp=${gpp}&gpp_sid=${gppSid}` - }; - } + // Makes the seed in the URLs predictable + sandbox.stub(crypto, 'getRandomValues').callsFake(function (buf) { + var bytes = [50, 5, 232, 133, 141, 55, 49, 57, 244, 126, 248, 44, 255, 38, 128, 0]; + for (var i = 0; i < bytes.length; i++) { + buf[i] = bytes[i]; + } + return buf; + }); + }); - function getSyncUrls(gdprApplies, gdprConsentString, usPrivacy, gpp, gppSid) { - var syncs = []; - for (var i = 0; i < 5; i++) { - syncs[i] = getSyncUrl(i, gdprApplies || 0, gdprConsentString || '', usPrivacy || '', gpp || '', gppSid || ''); - } - return syncs; - } + it('returns user syncs when an ID is present', function() { + expect(getUserSyncs()).to.deep.equal(buildSyncUrls()); + }); - function safelyRun(runExpectation) { - if (isActuallyOutdatedBrowser) { - expect(getUserSyncsWhenAllowed()).to.be.an('array').that.is.empty; - } else { - runExpectation(); - } - } + it('returns no syncs if there is no user ID', function() { + delete crb.clientId; + expect(getUserSyncs()).to.deep.equal([]); + }); - it('handles user syncs when there is a client id', function () { - turnOnClientId(); - safelyRun(() => expect(getUserSyncsWhenAllowed()).to.deep.equal(getSyncUrls())); + it('returns no syncs if there is no usp consent', function() { + expect(getUserSyncs(undefined, '1YYY')).to.deep.equal([]); }); - it('no user syncs when there is no client id', function () { - safelyRun(() => expect(getUserSyncsWhenAllowed()).to.be.an('array').that.is.empty); + it('returns no syncs if iframe syncing is not allowed', function() { + expect(spec.getUserSyncs({ iframeEnabled: false }, null, undefined, undefined, undefined)) + .to.deep.equal([]); + expect(spec.getUserSyncs({}, null, undefined, undefined, undefined)) + .to.deep.equal([]); }); - it('no user syncs when there is no us privacy consent', function () { - turnOnClientId(); - safelyRun(() => expect(getUserSyncsWhenAllowed(null, '1YYY')).to.be.an('array').that.is.empty); + it('includes the US privacy string in the sync URL if present', function() { + [ + '0---', + '1---', + '1NNN', + 'invalid', + 1234, + ].forEach(value => expect(getUserSyncs(undefined, value), `Value - ${value}`) + .to.deep.equal(buildSyncUrls(baseUrl.replace(/us_privacy=/, `us_privacy=${value}`)))); }); - it('pass through us privacy consent', function () { - turnOnClientId(); - safelyRun(() => expect(getUserSyncsWhenAllowed(null, '1YNY')).to.deep.equal(getSyncUrls(0, '', '1YNY'))); + it('includes gdpr information if provided', function() { + [ + { gdprApplies: true, consentString: 'test-consent-string', ga: '1', cs: 'test-consent-string' }, + { gdprApplies: false, consentString: 'test-consent-string', ga: '0', cs: 'test-consent-string' }, + { gdprApplies: true, ga: '1', cs: '' }, + { gdprApplies: false, ga: '0', cs: '' }, + { ga: '0', cs: '' }, + ].forEach(value => expect(getUserSyncs(value), `Value - ${value}`) + .to.deep.equal(buildSyncUrls(baseUrl + .replace(/gdpr=\d/, `gdpr=${value.ga}`) + .replace(/gdpr_consent=/, `gdpr_consent=${value.cs}`)))); }); - it('pass through gdpr consent', function () { - turnOnClientId(); - safelyRun(() => expect(getUserSyncsWhenAllowed({ gdprApplies: true, consentString: 'consentstring' })).to.deep.equal(getSyncUrls(1, 'consentstring', ''))); + it('handles malformed gdpr information', function() { + [ + null, + false, + true, + 1, + '1', + 'test-applies', + [], + {} + ].forEach(value => expect(getUserSyncs(value), `Value - ${JSON.stringify(value)}`) + .to.deep.equal(buildSyncUrls(baseUrl))); }); - it('pass through gpp consent', function () { - turnOnClientId(); - safelyRun(() => expect(getUserSyncsWhenAllowed(null, null, { consentString: 'gppString', applicableSections: [-1] })).to.deep.equal(getSyncUrls('', '', '', 'gppString', '-1'))); + it('includes gpp information if provided', function() { + [ + { applicableSections: [-1], consentString: 'test-consent-string', as: '-1', cs: 'test-consent-string' }, + { applicableSections: [1, 2, 3], consentString: 'test-consent-string', as: '1,2,3', cs: 'test-consent-string' }, + { applicableSections: [-1], as: '-1', cs: '' }, + { applicableSections: false, consentString: 'test-consent-string', as: '', cs: 'test-consent-string' }, + { applicableSections: null, consentString: 'test-consent-string', as: '', cs: 'test-consent-string' }, + { applicableSections: {}, consentString: 'test-consent-string', as: '', cs: 'test-consent-string' }, + { applicableSections: [], consentString: 'test-consent-string', as: '', cs: 'test-consent-string' }, + { as: '', cs: '' }, + ].forEach(value => expect(getUserSyncs(undefined, undefined, value), `Value - ${value}`) + .to.deep.equal(buildSyncUrls(baseUrl + .replace(/gpp=/, `gpp=${value.cs}`) + .replace(/gpp_sid=/, `gpp_sid=${value.as}`)))); }); - it('no user syncs when there is outdated browser', function () { - turnOnClientId(); - simulateOutdatedBrowser(); - safelyRun(() => expect(getUserSyncsWhenAllowed()).to.be.an('array').that.is.empty); + it('handles malformed gpp information', function() { + [ + null, + false, + true, + 1, + '1', + 'test-applies', + [], + {} + ].forEach(value => expect(getUserSyncs(undefined, undefined, value), `Value - ${JSON.stringify(value)}`) + .to.deep.equal(buildSyncUrls(baseUrl))); }); - it('no user syncs when no iframe syncing allowed', function () { - turnOnClientId(); - safelyRun(() => expect(getUserSyncsWhenForbidden()).to.be.an('array').that.is.empty); + it('includes all 3 if provided', function() { + expect(getUserSyncs( + { gdprApplies: true, consentString: 'test-gdpr-consent' }, + '1---', + { applicableSections: [1, 2, 3], consentString: 'test-gpp-consent' } + )).to.deep.equal(buildSyncUrls(baseUrl + .replace(/gdpr=\d/, 'gdpr=1') + .replace(/gdpr_consent=/, 'gdpr_consent=test-gdpr-consent') + .replace(/us_privacy=/, 'us_privacy=1---') + .replace(/gpp=/, 'gpp=test-gpp-consent') + .replace(/gpp_sid=/, 'gpp_sid=1,2,3'))); }); }); - describe('timeout pixel trigger', function () { - let triggerPixelStub; + describe('supportedMediaTypes', function() { + it('exposes video and banner', function() { + expect(spec.supportedMediaTypes).to.deep.equal([ 'banner', 'video' ]); + }); + }); + describe('onTimeout', function() { beforeEach(function () { - triggerPixelStub = sinon.stub(utils, 'triggerPixel'); + sinon.stub(utils, 'triggerPixel'); }); afterEach(function () { utils.triggerPixel.restore(); }); - it('should call triggerPixel utils function when timed out is filled', function () { - spec.onTimeout(); - expect(triggerPixelStub.getCall(0)).to.be.null; - spec.onTimeout([{ auctionId: '1234', timeout: 2000 }]); - expect(triggerPixelStub.getCall(0)).to.not.be.null; - expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://krk2.kargo.com/api/v1/event/timeout'); - expect(triggerPixelStub.getCall(0).args[0]).to.include('aid=1234'); - expect(triggerPixelStub.getCall(0).args[0]).to.include('ato=2000'); + it('does not call triggerPixel if timeout data is not provided', function() { + spec.onTimeout(null); + expect(utils.triggerPixel.callCount).to.equal(0); + }); + + it('calls triggerPixel if any timeout data is provided', function() { + spec.onTimeout([ + {auctionId: 'test-auction-id', timeout: 400}, + {auctionId: 'test-auction-id-2', timeout: 100}, + {auctionId: 'test-auction-id-3', timeout: 450}, + {auctionId: 'test-auction-id-4', timeout: 500}, + ]); + expect(utils.triggerPixel.calledWith('https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id&ato=400')).to.be.true; + expect(utils.triggerPixel.calledWith('https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id-2&ato=100')).to.be.true; + expect(utils.triggerPixel.calledWith('https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id-3&ato=450')).to.be.true; + expect(utils.triggerPixel.calledWith('https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id-4&ato=500')).to.be.true; }); }); });