UNPKG

mk9-prebid

Version:

Header Bidding Management Library

1,534 lines (1,296 loc) 52.9 kB
import { expect } from 'chai'; import { _features, internal as adagio, adagioScriptFromLocalStorageCb, getAdagioScript, storage, setExtraParam, spec, ENDPOINT, VERSION, RENDERER_URL, GlobalExchange } from '../../../modules/adagioBidAdapter.js'; import { loadExternalScript } from '../../../src/adloader.js'; import * as utils from '../../../src/utils.js'; import { config } from '../../../src/config.js'; import { NATIVE } from '../../../src/mediaTypes.js'; import * as prebidGlobal from 'src/prebidGlobal.js'; import { executeRenderer } from '../../../src/Renderer.js'; const BidRequestBuilder = function BidRequestBuilder(options) { const defaults = { request: { auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', adUnitCode: 'adunit-code', bidder: 'adagio' }, params: { organizationId: '1000', placement: 'PAVE_ATF', site: 'SITE-NAME', adUnitElementId: 'gpt-adunit-code' }, sizes: [[300, 250], [300, 600]], }; const request = { ...defaults.request, ...options }; this.withParams = (options) => { request.params = { ...defaults.params, ...options }; return this; }; this.build = () => request; }; const BidderRequestBuilder = function BidderRequestBuilder(options) { const defaults = { bidderCode: 'adagio', auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', bidderRequestId: '7g36s867Tr4xF90X', timeout: 3000, refererInfo: { numIframes: 0, reachedTop: true, referer: 'http://test.io/index.html?pbjs_debug=true' } }; const request = { ...defaults, ...options }; this.build = () => request; }; describe('Adagio bid adapter', () => { let adagioMock; let utilsMock; let sandbox; const fixtures = { getElementById(width, height, x, y) { const obj = { x: x || 800, y: y || 300, width: width || 300, height: height || 250, }; return { ...obj, getBoundingClientRect: () => { return { width: obj.width, height: obj.height, left: obj.x, top: obj.y, right: obj.x + obj.width, bottom: obj.y + obj.height }; } }; } }; // safeFrame implementation const $sf = { ext: { geom: function() {} } }; beforeEach(() => { window.ADAGIO = {}; window.ADAGIO.adUnits = {}; window.ADAGIO.pbjsAdUnits = []; window.ADAGIO.queue = []; window.ADAGIO.versions = {}; window.ADAGIO.versions.adagioBidderAdapter = VERSION; window.ADAGIO.pageviewId = 'dda61753-4059-4f75-b0bf-3f60bd2c4d9a'; GlobalExchange.clearFeatures(); GlobalExchange.clearExchangeData(); adagioMock = sinon.mock(adagio); utilsMock = sinon.mock(utils); sandbox = sinon.createSandbox(); }); afterEach(() => { window.ADAGIO = undefined; adagioMock.restore(); utilsMock.restore(); sandbox.restore(); }); describe('get and set params at adUnit level from global Prebid configuration', function() { it('should set params get from ortb2 config or bidderSettings. Priority to bidderSetting', function() { const bid = new BidRequestBuilder().build(); sandbox.stub(config, 'getConfig').callsFake(key => { const config = { adagio: { pagetype: 'article' }, ortb2: { site: { ext: { data: { environment: 'desktop', pagetype: 'abc' } } } } }; return utils.deepAccess(config, key); }); setExtraParam(bid, 'environment'); expect(bid.params.environment).to.equal('desktop'); setExtraParam(bid, 'pagetype') expect(bid.params.pagetype).to.equal('article'); }); it('should use the adUnit param unit if defined', function() { const bid = new BidRequestBuilder({ params: { pagetype: 'article' } }).build(); sandbox.stub(config, 'getConfig').withArgs('adagio').returns({ pagetype: 'ignore-me' }); setExtraParam(bid, 'pagetype') expect(bid.params.pagetype).to.equal('article'); }); }) describe('isBidRequestValid()', function() { it('should return true when required params have been found', function() { const bid = new BidRequestBuilder().withParams().build(); expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should compute organizationId and site params from global BidderSettings config', function() { sandbox.stub(adagio, 'getRefererInfo').returns({ reachedTop: true }); sandbox.stub(config, 'getConfig').withArgs('adagio').returns({ siteId: '1000:SITE-NAME' }); const bid = new BidRequestBuilder({ params: { placement: 'PAVE_ATF' } }).build(); expect(spec.isBidRequestValid(bid)).to.equal(true); }) it('should return false if bid.params is missing', function() { sandbox.spy(utils, 'logWarn'); const bid01 = new BidRequestBuilder().build(); expect(spec.isBidRequestValid(bid01)).to.equal(false); sinon.assert.callCount(utils.logWarn, 1); }); it('should use adUnit code for adUnitElementId and placement params', function() { const bid01 = new BidRequestBuilder({ params: { organizationId: '1000', site: 'site-name', useAdUnitCodeAsPlacement: true, useAdUnitCodeAsAdUnitElementId: true }}).build(); expect(spec.isBidRequestValid(bid01)).to.equal(true); expect(bid01.params.adUnitElementId).to.equal('adunit-code'); expect(bid01.params.placement).to.equal('adunit-code'); }); it('should return false when a required param is missing', function() { const bid01 = new BidRequestBuilder({ params: { organizationId: '1000', placement: 'PAVE_ATF' }}).build(); const bid02 = new BidRequestBuilder({ params: { organizationId: '1000', site: 'SITE-NAME' }}).build(); const bid03 = new BidRequestBuilder({ params: { placement: 'PAVE_ATF', site: 'SITE-NAME' }}).build(); sandbox.stub(config, 'getConfig').withArgs('adagio').returns({ siteId: '1000' }); const bid04 = new BidRequestBuilder({ params: { placement: 'PAVE_ATF' } }).build(); expect(spec.isBidRequestValid(bid01)).to.equal(false); expect(spec.isBidRequestValid(bid02)).to.equal(false); expect(spec.isBidRequestValid(bid03)).to.equal(false); expect(spec.isBidRequestValid(bid04)).to.equal(false); }); it('should return false when refererInfo.reachedTop is false', function() { sandbox.spy(utils, 'logWarn'); sandbox.stub(adagio, 'getRefererInfo').returns({ reachedTop: false }); const bid = new BidRequestBuilder().withParams().build(); expect(spec.isBidRequestValid(bid)).to.equal(false); sinon.assert.callCount(utils.logWarn, 1); sinon.assert.calledWith(utils.logWarn, 'Adagio: the main page url is unreachabled.'); }); }); describe('buildRequests()', function() { const expectedDataKeys = [ 'id', 'organizationId', 'secure', 'device', 'site', 'pageviewId', 'adUnits', 'regs', 'user', 'schain', 'prebidVersion', 'featuresVersion', 'data' ]; it('groups requests by organizationId', function() { const bid01 = new BidRequestBuilder().withParams().build(); const bid02 = new BidRequestBuilder().withParams().build(); const bid03 = new BidRequestBuilder().withParams({ organizationId: '1002' }).build(); const bidderRequest = new BidderRequestBuilder().build(); const requests = spec.buildRequests([bid01, bid02, bid03], bidderRequest); expect(requests).to.have.lengthOf(2); expect(requests[0].data.organizationId).to.equal('1000'); expect(requests[0].data.adUnits).to.have.lengthOf(2); expect(requests[1].data.organizationId).to.equal('1002'); expect(requests[1].data.adUnits).to.have.lengthOf(1); }); it('should send bid request to ENDPOINT_PB via POST', function() { sandbox.stub(adagio, 'getDevice').returns({ a: 'a' }); sandbox.stub(adagio, 'getSite').returns({ domain: 'adagio.io', 'page': 'https://adagio.io/hb' }); sandbox.stub(adagio, 'getPageviewId').returns('1234-567'); const bid01 = new BidRequestBuilder().withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); const requests = spec.buildRequests([bid01], bidderRequest); expect(requests).to.have.lengthOf(1); expect(requests[0].method).to.equal('POST'); expect(requests[0].url).to.equal(ENDPOINT); expect(requests[0].options.contentType).to.eq('text/plain'); expect(requests[0].data).to.have.all.keys(expectedDataKeys); }); it('should enqueue computed features for collect usage', function() { sandbox.stub(Date, 'now').returns(12345); const bid01 = new BidRequestBuilder().withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); adagioMock.expects('enqueue').withArgs(sinon.match({ action: 'features' })).atLeast(1); const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data).to.have.all.keys(expectedDataKeys); adagioMock.verify(); }); it('should filter some props in case refererDetection.reachedTop is false', function() { const bid01 = new BidRequestBuilder().withParams().build(); const bidderRequest = new BidderRequestBuilder({ refererInfo: { numIframes: 2, reachedTop: false, referer: 'http://example.com/iframe1.html', stack: [ null, 'http://example.com/iframe1.html', 'http://example.com/iframe2.html' ], canonicalUrl: '' } }).build(); const requests = spec.buildRequests([bid01], bidderRequest); expect(requests).to.have.lengthOf(1); expect(requests[0].data).to.have.all.keys(expectedDataKeys); expect(requests[0].data.adUnits[0].features).to.exist; expect(requests[0].data.adUnits[0].features.url).to.not.exist; }); describe('With video mediatype', function() { context('Outstream video', function() { it('should logWarn if user does not set renderer.backupOnly: true', function() { sandbox.spy(utils, 'logWarn'); const bid01 = new BidRequestBuilder({ adUnitCode: 'adunit-code-01', mediaTypes: { banner: { sizes: [[300, 250]] }, video: { context: 'outstream', playerSize: [[300, 250]], renderer: { url: 'https://url.tld', render: () => true } } }, }).withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); const request = spec.buildRequests([bid01], bidderRequest)[0]; expect(request.data.adUnits[0].mediaTypes.video.playerName).to.equal('other'); sinon.assert.calledWith(utils.logWarn, 'Adagio: renderer.backupOnly has not been set. Adagio recommends to use its own player to get expected behavior.'); }); }); it('Update mediaTypes.video with OpenRTB options. Validate and sanitize whitelisted OpenRTB', function() { sandbox.spy(utils, 'logWarn'); const bid01 = new BidRequestBuilder({ adUnitCode: 'adunit-code-01', mediaTypes: { banner: { sizes: [[300, 250]] }, video: { context: 'outstream', playerSize: [[300, 250]], mimes: ['video/mp4'], api: 5, // will be removed because invalid playbackmethod: [7], // will be removed because invalid } }, }).withParams({ // options in video, will overide video: { skip: 1, skipafter: 4, minduration: 10, maxduration: 30, placement: [3], protocols: [8] } }).build(); const bidderRequest = new BidderRequestBuilder().build(); const expected = { context: 'outstream', playerSize: [[300, 250]], playerName: 'adagio', mimes: ['video/mp4'], skip: 1, skipafter: 4, minduration: 10, maxduration: 30, placement: [3], protocols: [8], w: 300, h: 250 }; const requests = spec.buildRequests([bid01], bidderRequest); expect(requests).to.have.lengthOf(1); expect(requests[0].data.adUnits[0].mediaTypes.video).to.deep.equal(expected); sinon.assert.calledTwice(utils.logWarn.withArgs(sinon.match(new RegExp(/^Adagio: The OpenRTB/)))); }); }); describe('with sChain', function() { const schain = { ver: '1.0', complete: 1, nodes: [{ asi: 'ssp.test', sid: '00001', hp: 1 }] }; it('should add the schain if available at bidder level', function() { const bid01 = new BidRequestBuilder({ schain }).withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data).to.have.all.keys(expectedDataKeys); expect(requests[0].data.schain).to.deep.equal(schain); }); it('Schain should not be added to the request', function() { const bid01 = new BidRequestBuilder().withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data.schain).to.not.exist; }); }); describe('with GDPR', function() { const bid01 = new BidRequestBuilder().withParams().build(); const consentString = 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='; const gdprConsentBuilderTCF1 = function gdprConsentBuilderTCF1(applies, allows) { return { consentString, gdprApplies: applies, allowAuctionWithoutConsent: allows, apiVersion: 1 }; }; const gdprConsentBuilderTCF2 = function gdprConsentBuilderTCF2(applies) { return { consentString, gdprApplies: applies, apiVersion: 2 }; }; context('When GDPR applies', function() { it('send data.gdpr object to the server from TCF v.1.1 cmp', function() { const bidderRequest = new BidderRequestBuilder({ gdprConsent: gdprConsentBuilderTCF1(true, true) }).build(); const expected = { consentString, allowAuctionWithoutConsent: 1, consentRequired: 1, apiVersion: 1 }; const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data.regs.gdpr).to.deep.equal(expected); }); it('send data.gdpr object to the server from TCF v.2 cmp', function() { const bidderRequest = new BidderRequestBuilder({ gdprConsent: gdprConsentBuilderTCF2(true) }).build(); const expected = { consentString, allowAuctionWithoutConsent: 0, consentRequired: 1, apiVersion: 2 }; const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data.regs.gdpr).to.deep.equal(expected); }); }); context('When GDPR does not applies', function() { it('send data.gdpr object to the server from TCF v.1.1 cmp', function() { const bidderRequest = new BidderRequestBuilder({ gdprConsent: gdprConsentBuilderTCF1(false, true) }).build(); const expected = { consentString, allowAuctionWithoutConsent: 1, consentRequired: 0, apiVersion: 1 }; const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data.regs.gdpr).to.deep.equal(expected); }); it('send data.gdpr object to the server from TCF v.2 cmp', function() { const bidderRequest = new BidderRequestBuilder({ gdprConsent: gdprConsentBuilderTCF2(false) }).build(); const expected = { consentString, consentRequired: 0, allowAuctionWithoutConsent: 0, apiVersion: 2 }; const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data.regs.gdpr).to.deep.equal(expected); }); }); context('When GDPR is undefined in bidderRequest', function() { it('send an empty data.gdpr to the server', function() { const bidderRequest = new BidderRequestBuilder().build(); const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data.regs.gdpr).to.be.empty; }); }); }); describe('with COPPA', function() { const bid01 = new BidRequestBuilder().withParams().build(); it('should send the Coppa "required" flag set to "1" in the request', function () { const bidderRequest = new BidderRequestBuilder().build(); sinon.stub(config, 'getConfig') .withArgs('coppa') .returns(true); const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data.regs.coppa.required).to.equal(1); config.getConfig.restore(); }); }); describe('without COPPA', function() { const bid01 = new BidRequestBuilder().withParams().build(); it('should send the Coppa "required" flag set to "0" in the request', function () { const bidderRequest = new BidderRequestBuilder().build(); const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data.regs.coppa.required).to.equal(0); }); }); describe('with USPrivacy', function() { const bid01 = new BidRequestBuilder().withParams().build(); const consent = 'Y11N'; it('should send the USPrivacy "ccpa.uspConsent" in the request', function () { const bidderRequest = new BidderRequestBuilder({ uspConsent: consent }).build(); const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data.regs.ccpa.uspConsent).to.equal(consent); }); }); describe('without USPrivacy', function() { const bid01 = new BidRequestBuilder().withParams().build(); it('should have an empty "ccpa" field in the request', function () { const bidderRequest = new BidderRequestBuilder().build(); const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data.regs.ccpa).to.be.empty; }); }); describe('with userID modules', function() { const userId = { pubcid: '01EAJWWNEPN3CYMM5N8M5VXY22', unsuported: '666' }; it('should send "user.eids" in the request for Prebid.js supported modules only', function() { const bid01 = new BidRequestBuilder({ userId }).withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); const requests = spec.buildRequests([bid01], bidderRequest); const expected = [{ source: 'pubcid.org', uids: [ { atype: 1, id: '01EAJWWNEPN3CYMM5N8M5VXY22' } ] }]; expect(requests[0].data.user.eids).to.have.lengthOf(1); expect(requests[0].data.user.eids).to.deep.equal(expected); }); it('should send an empty "user.eids" array in the request if userId module is unsupported', function() { const bid01 = new BidRequestBuilder({ userId: { unsuported: '666' } }).withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data.user.eids).to.be.empty; }); }); describe('with priceFloors module', function() { it('should get and set floor by mediatype and sizes', function() { const bid01 = new BidRequestBuilder({ mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] }, video: { playerSize: [600, 480] } } }).withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); // delete the computed `sizes` prop as we are based on mediaTypes only. delete bid01.sizes bid01.getFloor = () => { return { floor: 1, currency: 'USD' } } const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data.adUnits[0].floors.length).to.equal(3); expect(requests[0].data.adUnits[0].floors[0]).to.deep.equal({f: 1, mt: 'banner', s: '300x250'}); expect(requests[0].data.adUnits[0].floors[1]).to.deep.equal({f: 1, mt: 'banner', s: '300x600'}); expect(requests[0].data.adUnits[0].floors[2]).to.deep.equal({f: 1, mt: 'video', s: '600x480'}); }); it('should get and set floor by mediatype if no size provided (ex native, video)', function() { const bid01 = new BidRequestBuilder({ mediaTypes: { video: { context: 'outstream', mimes: ['video/mp4'] }, native: { body: { required: true } } } }).withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); bid01.getFloor = () => { return { floor: 1, currency: 'USD' } } const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data.adUnits[0].floors.length).to.equal(2); expect(requests[0].data.adUnits[0].floors[0]).to.deep.equal({f: 1, mt: 'video'}); expect(requests[0].data.adUnits[0].floors[1]).to.deep.equal({f: 1, mt: 'native'}); }); it('should get and set floor with default value if no floors found', function() { const bid01 = new BidRequestBuilder({ mediaTypes: { video: { context: 'outstream', mimes: ['video/mp4'] } } }).withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); bid01.getFloor = () => { return { floor: NaN, currency: 'USD' } } const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data.adUnits[0].floors.length).to.equal(1); expect(requests[0].data.adUnits[0].floors[0]).to.deep.equal({f: 0.1, mt: 'video'}); }); }); }); describe('interpretResponse()', function() { let serverResponse = { body: { data: { pred: 1 }, bids: [{ ad: '<div style="background-color:red; height:250px; width:300px"></div>', cpm: 1, creativeId: 'creativeId', currency: 'EUR', height: 250, netRevenue: true, requestId: 'c180kg4267tyqz', ttl: 360, width: 300, aDomain: ['advertiser.com'], mediaType: 'banner', meta: { advertiserId: '80', advertiserName: 'An Advertiser', networkId: '110' } }] } }; let bidRequest = { data: { adUnits: [{ bidder: 'adagio', params: { organizationId: '1000', placement: 'PAVE_ATF', site: 'SITE-NAME', adUnitElementId: 'gpt-adunit-code', pagetype: 'ARTICLE', category: 'NEWS', subcategory: 'SPORT', environment: 'desktop', supportIObs: true }, adUnitCode: 'adunit-code', mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, bidId: 'c180kg4267tyqz', bidderRequestId: '8vfscuixrovn8i', auctionId: 'lel4fhp239i9km', pageviewId: 'd8c4fl2k39i0wn', }] } }; it('should return an empty response array if body is empty', function() { expect(spec.interpretResponse({ body: null }, bidRequest)).to.be.an('array').length(0); expect(spec.interpretResponse({ body: {} }, bidRequest)).to.be.an('array').length(0); }); it('should return an empty response array if bids array is empty', function() { expect(spec.interpretResponse({ body: { bids: [] } }, bidRequest)).to.be.an('array').length(0); }); it('should handle properly a correct bid response', function() { let expectedResponse = [{ ad: '<div style="background-color:red; height:250px; width:300px"></div>', cpm: 1, creativeId: 'creativeId', currency: 'EUR', height: 250, netRevenue: true, requestId: 'c180kg4267tyqz', ttl: 360, width: 300, placement: 'PAVE_ATF', site: 'SITE-NAME', pagetype: 'ARTICLE', category: 'NEWS', subcategory: 'SPORT', environment: 'desktop', aDomain: ['advertiser.com'], mediaType: 'banner', meta: { advertiserId: '80', advertiserName: 'An Advertiser', advertiserDomains: ['advertiser.com'], networkId: '110', mediaType: 'banner' } }]; expect(spec.interpretResponse(serverResponse, bidRequest)).to.be.an('array'); expect(spec.interpretResponse(serverResponse, bidRequest)).to.deep.equal(expectedResponse); }); it('Meta props should be limited if no bid.meta is provided', function() { const altServerResponse = utils.deepClone(serverResponse); delete altServerResponse.body.bids[0].meta; let expectedResponse = [{ ad: '<div style="background-color:red; height:250px; width:300px"></div>', cpm: 1, creativeId: 'creativeId', currency: 'EUR', height: 250, netRevenue: true, requestId: 'c180kg4267tyqz', ttl: 360, width: 300, placement: 'PAVE_ATF', site: 'SITE-NAME', pagetype: 'ARTICLE', category: 'NEWS', subcategory: 'SPORT', environment: 'desktop', aDomain: ['advertiser.com'], mediaType: 'banner', meta: { advertiserDomains: ['advertiser.com'], mediaType: 'banner' } }]; expect(spec.interpretResponse(altServerResponse, bidRequest)).to.deep.equal(expectedResponse); }); it('should populate ADAGIO queue with ssp-data', function() { sandbox.stub(Date, 'now').returns(12345); adagioMock.expects('enqueue').withExactArgs({ action: 'ssp-data', ts: 12345, data: serverResponse.body.data }).once(); spec.interpretResponse(serverResponse, bidRequest); adagioMock.verify(); }); it('should properly try-catch an exception and return an empty array', function() { sandbox.stub(adagio, 'enqueue').throws(); utilsMock.expects('logError').once(); expect(spec.interpretResponse(serverResponse, bidRequest)).to.be.an('array').length(0); utilsMock.verify(); }); describe('Response with video outstream', () => { const bidRequestWithOutstream = utils.deepClone(bidRequest); bidRequestWithOutstream.data.adUnits[0].mediaTypes.video = { context: 'outstream', playerSize: [[300, 250]], mimes: ['video/mp4'], skip: true }; const serverResponseWithOutstream = utils.deepClone(serverResponse); serverResponseWithOutstream.body.bids[0].vastXml = '<VAST version="4.0"><Ad></Ad></VAST>'; serverResponseWithOutstream.body.bids[0].mediaType = 'video'; serverResponseWithOutstream.body.bids[0].outstream = { bvwUrl: 'https://foo.baz', impUrl: 'https://foo.bar' }; it('should set a renderer in video outstream context', function() { const bidResponse = spec.interpretResponse(serverResponseWithOutstream, bidRequestWithOutstream)[0]; expect(bidResponse).to.have.any.keys('outstream', 'renderer', 'mediaType'); expect(bidResponse.renderer).to.be.a('object'); expect(bidResponse.renderer.url).to.equal(RENDERER_URL); expect(bidResponse.renderer.config.bvwUrl).to.be.ok; expect(bidResponse.renderer.config.impUrl).to.be.ok; expect(bidResponse.renderer.loaded).to.not.be.ok; expect(bidResponse.width).to.equal(300); expect(bidResponse.height).to.equal(250); expect(bidResponse.vastUrl).to.match(/^data:text\/xml;/) }); it('should execute Adagio outstreamPlayer if defined', function() { window.ADAGIO.outstreamPlayer = sinon.stub(); const bidResponse = spec.interpretResponse(serverResponseWithOutstream, bidRequestWithOutstream)[0]; executeRenderer(bidResponse.renderer, bidResponse) sinon.assert.calledOnce(window.ADAGIO.outstreamPlayer); delete window.ADAGIO.outstreamPlayer; }); it('should logError if Adagio outstreamPlayer is not defined', function() { const bidResponse = spec.interpretResponse(serverResponseWithOutstream, bidRequestWithOutstream)[0]; executeRenderer(bidResponse.renderer, bidResponse) utilsMock.expects('logError').withExactArgs('Adagio: Adagio outstream player is not defined').once(); }); }); describe('Response with native add', () => { const serverResponseWithNative = utils.deepClone(serverResponse) serverResponseWithNative.body.bids[0].mediaType = 'native'; serverResponseWithNative.body.bids[0].admNative = { ver: '1.2', link: { url: 'https://i.am.a.click.url', clicktrackers: [ 'https://i.am.a.clicktracker.url' ] }, privacy: 'http://www.myprivacyurl.url', ext: { bvw: 'test' }, eventtrackers: [ { event: 1, method: 1, url: 'https://eventrack.local/impression' }, { event: 1, method: 2, url: 'https://eventrack.local/impression' }, { event: 2, method: 1, url: 'https://eventrack.local/viewable-mrc50' } ], assets: [ { title: { text: 'My title' } }, { img: { url: 'https://images.local/image.jpg', w: 100, h: 250 } }, { img: { type: 1, url: 'https://images.local/icon.png', w: 40, h: 40 } }, { data: { type: 1, // sponsored value: 'Adagio' } }, { data: { type: 2, // desc / body value: 'The super ad text' } }, { data: { type: 3, // rating value: '10 from 10' } }, { data: { type: 11, // displayUrl value: 'https://i.am.a.display.url' } } ] }; const bidRequestNative = utils.deepClone(bidRequest) bidRequestNative.nativeParams = { sendTargetingKeys: false, clickUrl: { required: true, }, title: { required: true, }, body: { required: true, }, sponsoredBy: { required: false }, image: { required: true }, icon: { required: true }, privacyLink: { required: false }, ext: { adagio_bvw: {} } }; it('Should ignore native parsing due to missing raw admNative property', () => { const alternateServerResponse = utils.deepClone(serverResponseWithNative); delete alternateServerResponse.body.bids[0].admNative const r = spec.interpretResponse(alternateServerResponse, bidRequestNative); expect(r[0].mediaType).to.equal(NATIVE); expect(r[0].native).not.ok; utilsMock.expects('logError').once(); }); it('Should ignore native parsing due to invalid raw admNative.assets property', () => { const alternateServerResponse = utils.deepClone(serverResponseWithNative); alternateServerResponse.body.bids[0].admNative.assets = { title: { text: 'test' } }; const r = spec.interpretResponse(alternateServerResponse, bidRequestNative); expect(r[0].mediaType).to.equal(NATIVE); expect(r[0].native).not.ok; utilsMock.expects('logError').once(); }); it('Should handle and return a formated Native ad', () => { const r = spec.interpretResponse(serverResponseWithNative, bidRequestNative); const expected = { displayUrl: 'https://i.am.a.display.url', sponsoredBy: 'Adagio', body: 'The super ad text', rating: '10 from 10', clickUrl: 'https://i.am.a.click.url', title: 'My title', impressionTrackers: [ 'https://eventrack.local/impression' ], javascriptTrackers: '<script src=\"https://eventrack.local/impression\"></script>', clickTrackers: [ 'https://i.am.a.clicktracker.url' ], image: { url: 'https://images.local/image.jpg', width: 100, height: 250 }, icon: { url: 'https://images.local/icon.png', width: 40, height: 40 }, ext: { adagio_bvw: 'test' }, privacyLink: 'http://www.myprivacyurl.url' } expect(r[0].mediaType).to.equal(NATIVE); expect(r[0].native).ok; expect(r[0].native).to.deep.equal(expected); }); }); }); describe('getUserSyncs()', function() { const syncOptions = { syncEnabled: false }; it('should handle user syncs if data is in the server response ', function() { const serverResponses = [{ body: { userSyncs: [ { t: 'i', u: 'https://test.url.com/setuid' }, { t: 'p', u: 'https://test.url.com/setuid' } ] } }]; let result = spec.getUserSyncs(syncOptions, serverResponses); expect(result[0].type).to.equal('iframe'); expect(result[0].url).contain('setuid'); expect(result[1].type).to.equal('image'); expect(result[1].url).contain('setuid'); }); it('should return false if data is not in server response', function() { const serverResponse = [{ body: '' }]; const result = spec.getUserSyncs(syncOptions, serverResponse); expect(result).to.equal(false); }); }); describe('transformBidParams', function() { it('Compute additional params in s2s mode', function() { GlobalExchange.prepareExchangeData('{}'); sandbox.stub(window.top.document, 'getElementById').returns( fixtures.getElementById() ); sandbox.stub(window.top, 'getComputedStyle').returns({ display: 'block' }); sandbox.stub(utils, 'inIframe').returns(false); const adUnit = { code: 'adunit-code', params: { organizationId: '1000' } }; const bid01 = new BidRequestBuilder({ 'mediaTypes': { banner: { sizes: [[300, 250]] }, video: { context: 'outstream', playerSize: [300, 250], renderer: { url: 'https://url.tld', render: () => true } } } }).withParams().build(); const params = spec.transformBidParams({ organizationId: '1000' }, true, adUnit, [{ bidderCode: 'adagio', auctionId: bid01.auctionId, bids: [bid01] }]); expect(params.organizationId).to.exist; expect(params.auctionId).to.exist; expect(params.playerName).to.exist; expect(params.playerName).to.equal('other'); expect(params.features).to.exist; expect(params.features.page_dimensions).to.exist; expect(params.features.adunit_position).to.exist; expect(params.features.dom_loading).to.exist; expect(params.features.print_number).to.exist; expect(params.features.user_timestamp).to.exist; expect(params.placement).to.exist; expect(params.adUnitElementId).to.exist; expect(params.site).to.exist; expect(params.data.session).to.exist; }); }); describe('Adagio features when prebid in top.window', function() { it('should return all expected features when all expected bidder params are available', function() { sandbox.stub(window.top.document, 'getElementById').returns( fixtures.getElementById() ); sandbox.stub(window.top, 'getComputedStyle').returns({ display: 'block' }); sandbox.stub(utils, 'inIframe').returns(false); const bidRequest = new BidRequestBuilder({ 'mediaTypes': { banner: { sizes: [[300, 250]] } } }).withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); const requests = spec.buildRequests([bidRequest], bidderRequest); const result = requests[0].data.adUnits[0].features; expect(result.adunit_position).to.match(/^[\d]+x[\d]+$/); expect(result.page_dimensions).to.match(/^[\d]+x[\d]+$/); expect(result.viewport_dimensions).to.match(/^[\d]+x[\d]+$/); expect(result.print_number).to.be.a('String'); expect(result.dom_loading).to.be.a('String'); expect(result.user_timestamp).to.be.a('String'); expect(result.url).to.not.exist; expect(result.device).to.not.exist; expect(result.os).to.not.exist; expect(result.browser).to.not.exist; }); it('should return all expected features when `adUnitElementId` param is not available', function() { sandbox.stub(utils, 'inIframe').returns(false); const bidRequest = new BidRequestBuilder({ params: { organizationId: '1000', placement: 'PAVE_ATF', site: 'SITE-NAME' }, 'mediaTypes': { banner: { sizes: [[300, 250]] } } }).build(); const bidderRequest = new BidderRequestBuilder().build(); const requests = spec.buildRequests([bidRequest], bidderRequest); const result = requests[0].data.adUnits[0].features; expect(result.adunit_position).to.not.exist; expect(result.page_dimensions).to.be.a('String'); expect(result.viewport_dimensions).to.be.a('String'); expect(result.print_number).to.be.a('String'); expect(result.dom_loading).to.be.a('String'); expect(result.user_timestamp).to.be.a('String'); }); }); describe('Adagio features when prebid in Safeframe', function() { beforeEach(function () { window.$sf = $sf; }); afterEach(function () { delete window.$sf; }); it('should return all expected features when prebid is in safeframe iframe', function() { sandbox.stub(window.$sf.ext, 'geom').returns({ win: {t: 23, r: 1920, b: 1200, l: 0, w: 1920, h: 1177}, self: {t: 210, r: 1159, b: 460, l: 859, w: 300, h: 250}, }); const bidRequest = new BidRequestBuilder({ 'mediaTypes': { banner: { sizes: [[300, 250]] } } }).withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); const requests = spec.buildRequests([bidRequest], bidderRequest); const result = requests[0].data.adUnits[0].features; expect(result.page_dimensions).to.not.exist; expect(result.viewport_dimensions).to.be.a('String'); expect(result.print_number).to.be.a('String'); expect(result.dom_loading).to.be.a('String'); expect(result.user_timestamp).to.be.a('String'); expect(result.adunit_position).to.exist; }); it('should return all expected features when prebid safeframe api not properly implemented', function() { const bidRequest = new BidRequestBuilder({ 'mediaTypes': { banner: { sizes: [[300, 250]] } } }).withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); const requests = spec.buildRequests([bidRequest], bidderRequest); const result = requests[0].data.adUnits[0].features; expect(result.page_dimensions).to.not.exist; expect(result.viewport_dimensions).to.not.exist; expect(result.print_number).to.be.a('String'); expect(result.dom_loading).to.be.a('String'); expect(result.user_timestamp).to.be.a('String'); expect(result.adunit_position).to.not.exist; }); it('should return all expected features when prebid safeframe api not properly implemented bis', function() { window.$sf.ext.geom = undefined; const bidRequest = new BidRequestBuilder({ 'mediaTypes': { banner: { sizes: [[300, 250]] } } }).withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); const requests = spec.buildRequests([bidRequest], bidderRequest); const result = requests[0].data.adUnits[0].features; expect(result.page_dimensions).to.not.exist; expect(result.viewport_dimensions).to.not.exist; expect(result.print_number).to.be.a('String'); expect(result.dom_loading).to.be.a('String'); expect(result.user_timestamp).to.be.a('String'); expect(result.adunit_position).to.not.exist; }); }); describe('Adagio features when prebid in crossdomain iframe', function() { it('should return all expected features', function() { sandbox.stub(utils, 'getWindowTop').throws(); const bidRequest = new BidRequestBuilder({ 'mediaTypes': { banner: { sizes: [[300, 250]] } } }).withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); const requests = spec.buildRequests([bidRequest], bidderRequest); const result = requests[0].data.adUnits[0].features; expect(result.page_dimensions).to.not.exist; expect(result.viewport_dimensions).to.not.exist; expect(result.print_number).to.be.a('String'); expect(result.dom_loading).to.be.a('String'); expect(result.user_timestamp).to.be.a('String'); expect(result.adunit_position).to.not.exist; }); }); describe.skip('optional params auto detection', function() { it('should auto detect environment', function() { const getDeviceStub = sandbox.stub(_features, 'getDevice'); getDeviceStub.returns(5); expect(adagio.autoDetectEnvironment()).to.eq('tablet'); getDeviceStub.returns(4); expect(adagio.autoDetectEnvironment()).to.eq('mobile'); getDeviceStub.returns(2); expect(adagio.autoDetectEnvironment()).to.eq('desktop'); }); it('should auto detect adUnitElementId when GPT is used', function() { sandbox.stub(utils, 'getGptSlotInfoForAdUnitCode').withArgs('banner').returns({divId: 'gpt-banner'}); expect(adagio.autoDetectAdUnitElementId('banner')).to.eq('gpt-banner'); }); }); describe.skip('print number handling', function() { it('should return 1 if no adunit-code found. This means it is the first auction', function() { sandbox.stub(adagio, 'getPageviewId').returns('abc-def'); expect(adagio.computePrintNumber('adunit-code')).to.eql(1); }); it('should increment the adunit print number when the adunit-code has already been used for an other auction', function() { sandbox.stub(adagio, 'getPageviewId').returns('abc-def'); window.top.ADAGIO.adUnits['adunit-code'] = { pageviewId: 'abc-def', printNumber: 1, }; expect(adagio.computePrintNumber('adunit-code')).to.eql(2); }); }); describe('site information using refererDetection or window.top', function() { it('should returns domain, page and window.referrer in a window.top context', function() { sandbox.stub(utils, 'getWindowTop').returns({ location: { hostname: 'test.io', href: 'https://test.io/article/a.html' }, document: { referrer: 'https://google.com' } }); const bidderRequest = new BidderRequestBuilder({ refererInfo: { numIframes: 0, reachedTop: true, referer: 'http://test.io/index.html?pbjs_debug=true' } }).build(); expect(adagio.getSite(bidderRequest)).to.deep.equal({ domain: 'test.io', page: 'https://test.io/article/a.html', referrer: 'https://google.com' }); }); it('should returns domain and page in a cross-domain w/ top domain reached context', function() { sandbox.stub(utils, 'getWindowTop').throws(); const info = { numIframes: 0, reachedTop: true, referer: 'http://level.io/', stack: [ 'http://level.io/', 'http://example.com/iframe1.html', 'http://example.com/iframe2.html' ], canonicalUrl: '' }; const bidderRequest = new BidderRequestBuilder({ refererInfo: info }).build(); expect(adagio.getSite(bidderRequest)).to.deep.equal({ domain: 'level.io', page: 'http://level.io/', referrer: '' }); }); it('should not return anything in a cross-domain w/o top domain reached and w/o ancestor context', function() { sandbox.stub(utils, 'getWindowTop').throws(); const info = { numIframes: 2, reachedTop: false, referer: 'http://example.com/iframe1.html', stack: [ null, 'http://example.com/iframe1.html', 'http://example.com/iframe2.html' ], canonicalUrl: '' }; const bidderRequest = new BidderRequestBuilder({ refererInfo: info }).build(); expect(adagio.getSite(bidderRequest)).to.deep.equal({ domain: '', page: '', referrer: '' }); }); it('should return domain only in a cross-domain w/o top domain reached and w/ ancestors context', function() { sandbox.stub(utils, 'getWindowTop').throws(); const info = { numIframes: 2, reachedTop: false, referer: 'http://example.com/iframe1.html', stack: [ 'http://mytest.com/', 'http://example.com/iframe1.html', 'http://example.com/iframe2.html' ], canonicalUrl: '' }; const bidderRequest = new BidderRequestBuilder({ refererInfo: info }).build(); expect(adagio.getSite(bidderRequest)).to.deep.equal({ domain: 'mytest.com', page: '', referrer: '' }); }); }); describe('adagioScriptFromLocalStorageCb()', function() { const VALID_HASH = 'Lddcw3AADdQDrPtbRJkKxvA+o1CtScGDIMNRpHB3NnlC/FYmy/9RKXelKrYj/sjuWusl5YcOpo+lbGSkk655i8EKuDiOvK6ae/imxSrmdziIp+S/TA6hTFJXcB8k1Q9OIp4CMCT52jjXgHwX6G0rp+uYoCR25B1jHaHnpH26A6I='; const INVALID_HASH = 'invalid'; const VALID_SCRIPT_CONTENT = 'var _ADAGIO=function(){};(_ADAGIO)();\n'; const INVALID_SCRIPT_CONTENT = 'var _ADAGIO=function(){//corrupted};(_ADAGIO)();\n'; const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; beforeEach(function() { localStorage.removeItem(ADAGIO_LOCALSTORAGE_KEY); }); describe('getAdagioScript', function() { it('should run storage.getDataFromLocalStorage callback and call adagioScriptFromLocalStorageCb() ', function() { sandbox.spy(adagio, 'adagioScriptFromLocalStorageCb'); const getDataFromLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage').callsArg(1); localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + VALID_SCRIPT_CONTENT); getAdagioScript(); sinon.assert.callCount(getDataFromLocalStorageStub, 1); sinon.assert.callCount(adagio.adagioScriptFromLocalStorageCb, 1); }); it('should load external script if the user consent', function() { sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, true); getAdagioScript(); expect(loadExternalScript.called).to.be.true; }); it('should not load external script if the user does not consent', function() { sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, false); getAdagioScript(); expect(loadExternalScript.called).to.be.false; }); it('should remove the localStorage key if exists and the user does not consent', function() { sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, false); localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, 'the script'); getAdagioScript(); expect(loadExternalScript.called).to.be.false; expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null;