UNPKG

mk9-prebid

Version:

Header Bidding Management Library

633 lines (560 loc) 22 kB
import { expect } from 'chai'; import { sharethroughAdapterSpec, sharethroughInternal } from 'modules/sharethroughBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import * as utils from '../../../src/utils.js'; import { config } from 'src/config'; const spec = newBidder(sharethroughAdapterSpec).getSpec(); const bidRequests = [ { bidder: 'sharethrough', bidId: 'bidId1', sizes: [[600, 300]], placementCode: 'foo', params: { pkey: 'aaaa1111' }, ortb2Imp: { ext: { data: { pbadslot: 'adslot-id-1' } } }, userId: { tdid: 'fake-tdid', pubcid: 'fake-pubcid', idl_env: 'fake-identity-link', id5id: { uid: 'fake-id5id', ext: { linkType: 2 } }, lipb: { lipbid: 'fake-lipbid' } }, crumbs: { pubcid: 'fake-pubcid-in-crumbs-obj' } }, { bidder: 'sharethrough', bidId: 'bidId2', sizes: [[700, 400]], placementCode: 'bar', params: { pkey: 'bbbb2222', iframe: true } }, { bidder: 'sharethrough', bidId: 'bidId3', sizes: [[700, 400]], placementCode: 'coconut', params: { pkey: 'cccc3333', iframe: true, iframeSize: [500, 500] } }, { bidder: 'sharethrough', bidId: 'bidId4', sizes: [[700, 400]], placementCode: 'bar', params: { pkey: 'dddd4444', badv: ['domain1.com', 'domain2.com'] } }, { bidder: 'sharethrough', bidId: 'bidId5', sizes: [[700, 400]], placementCode: 'bar', params: { pkey: 'eeee5555', bcat: ['IAB1-1', 'IAB1-2'] } }, ]; const prebidRequests = [ { method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', placement_key: 'pKey' }, strData: { skipIframeBusting: false, sizes: [] } }, { method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', placement_key: 'pKey' }, strData: { skipIframeBusting: true, sizes: [[300, 250], [300, 300], [250, 250], [600, 50]] } }, { method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', placement_key: 'pKey' }, strData: { skipIframeBusting: true, iframeSize: [500, 500], sizes: [[300, 250], [300, 300], [250, 250], [600, 50]] } }, { method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', placement_key: 'pKey' }, strData: { skipIframeBusting: false, sizes: [[0, 0]] } }, { method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', placement_key: 'pKey' }, strData: { skipIframeBusting: false, sizes: [[300, 250], [300, 300], [250, 250], [600, 50]] } } ]; const bidderResponse = { body: { 'adserverRequestId': '40b6afd5-6134-4fbb-850a-bb8972a46994', 'bidId': 'bidId1', 'version': 1, 'creatives': [{ 'auctionWinId': 'b2882d5e-bf8b-44da-a91c-0c11287b8051', 'cpm': 12.34, 'creative': { 'deal_id': 'aDealId', 'creative_key': 'aCreativeId', 'title': '✓ à la mode' } }], 'stxUserId': '' }, header: { get: (header) => header } }; const setUserAgent = (uaString) => { window.navigator['__defineGetter__']('userAgent', function() { return uaString; }); }; describe('sharethrough internal spec', function() { let windowStub, windowTopStub; let stubbedReturn = [{ appendChild: () => undefined }] beforeEach(function() { windowStub = sinon.stub(window.document, 'getElementsByTagName'); windowTopStub = sinon.stub(window.top.document, 'getElementsByTagName'); windowStub.withArgs('body').returns(stubbedReturn); windowTopStub.withArgs('body').returns(stubbedReturn); }); afterEach(function() { windowStub.restore(); windowTopStub.restore(); window.STR = undefined; window.top.STR = undefined; }); describe('we cannot access top level document', function() { beforeEach(function() { window.lockedInFrame = true; }); afterEach(function() { window.lockedInFrame = false; }); it('appends sfp.js to the safeframe', function() { sharethroughInternal.handleIframe(); expect(windowStub.calledOnce).to.be.true; }); it('does not append anything if sfp.js is already loaded in the safeframe', function() { window.STR = { Tag: true }; sharethroughInternal.handleIframe(); expect(windowStub.notCalled).to.be.true; expect(windowTopStub.notCalled).to.be.true; }); }); describe('we are able to bust out of the iframe', function() { it('appends sfp.js to window.top', function() { sharethroughInternal.handleIframe(); expect(windowStub.calledOnce).to.be.true; expect(windowTopStub.calledOnce).to.be.true; }); it('only appends sfp-set-targeting.js if sfp.js is already loaded on the page', function() { window.top.STR = { Tag: true }; sharethroughInternal.handleIframe(); expect(windowStub.calledOnce).to.be.true; expect(windowTopStub.notCalled).to.be.true; }); }); }); describe('sharethrough adapter spec', function() { describe('.code', function() { it('should return a bidder code of sharethrough', function() { expect(spec.code).to.eql('sharethrough'); }); }); describe('.isBidRequestValid', function() { it('should return false if req has no pkey', function() { const invalidBidRequest = { bidder: 'sharethrough', params: { notPKey: 'abc123' } }; expect(spec.isBidRequestValid(invalidBidRequest)).to.eql(false); }); it('should return false if req has wrong bidder code', function() { const invalidBidRequest = { bidder: 'notSharethrough', params: { notPKey: 'abc123' } }; expect(spec.isBidRequestValid(invalidBidRequest)).to.eql(false); }); it('should return true if req is correct', function() { expect(spec.isBidRequestValid(bidRequests[0])).to.eq(true); expect(spec.isBidRequestValid(bidRequests[1])).to.eq(true); }); }); describe('.buildRequests', function() { it('should return an array of requests', function() { const builtBidRequests = spec.buildRequests(bidRequests); expect(builtBidRequests[0].url).to.eq('https://btlr.sharethrough.com/WYu2BXv1/v1'); expect(builtBidRequests[1].url).to.eq('https://btlr.sharethrough.com/WYu2BXv1/v1'); expect(builtBidRequests[0].method).to.eq('POST'); }); it('should set the instant_play_capable parameter correctly based on browser userAgent string', function() { setUserAgent('Android Chrome/60'); let builtBidRequests = spec.buildRequests(bidRequests); expect(builtBidRequests[0].data.instant_play_capable).to.be.true; setUserAgent('iPhone Version/11'); builtBidRequests = spec.buildRequests(bidRequests); expect(builtBidRequests[0].data.instant_play_capable).to.be.true; setUserAgent('iPhone CriOS/60'); builtBidRequests = spec.buildRequests(bidRequests); expect(builtBidRequests[0].data.instant_play_capable).to.be.true; setUserAgent('Android Chrome/50'); builtBidRequests = spec.buildRequests(bidRequests); expect(builtBidRequests[0].data.instant_play_capable).to.be.false; setUserAgent('Android Chrome'); builtBidRequests = spec.buildRequests(bidRequests); expect(builtBidRequests[0].data.instant_play_capable).to.be.false; setUserAgent(undefined); builtBidRequests = spec.buildRequests(bidRequests); expect(builtBidRequests[0].data.instant_play_capable).to.be.false; }); it('should set the secure parameter to false when the protocol is http', function() { const stub = sinon.stub(sharethroughInternal, 'getProtocol').returns('http:'); const bidRequest = spec.buildRequests(bidRequests, null)[0]; expect(bidRequest.data.secure).to.be.false; stub.restore(); }); it('should set the secure parameter to true when the protocol is https', function() { const stub = sinon.stub(sharethroughInternal, 'getProtocol').returns('https:'); const bidRequest = spec.buildRequests(bidRequests, null)[0]; expect(bidRequest.data.secure).to.be.true; stub.restore(); }); it('should set the secure parameter to true when the protocol is neither http or https', function() { const stub = sinon.stub(sharethroughInternal, 'getProtocol').returns('about:'); const bidRequest = spec.buildRequests(bidRequests, null)[0]; expect(bidRequest.data.secure).to.be.true; stub.restore(); }); it('should add ccpa parameter if uspConsent is present', function() { const uspConsent = '1YNN'; const bidderRequest = { uspConsent: uspConsent }; const bidRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(bidRequest.data.us_privacy).to.eq(uspConsent); }); it('should add consent parameters if gdprConsent is present', function() { const gdprConsent = { consentString: 'consent_string123', gdprApplies: true }; const bidderRequest = { gdprConsent: gdprConsent }; const bidRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(bidRequest.data.consent_required).to.eq(true); expect(bidRequest.data.consent_string).to.eq('consent_string123'); }); it('should handle gdprConsent is present but values are undefined case', function() { const gdprConsent = { consent_string: undefined, gdprApplies: undefined }; const bidderRequest = { gdprConsent: gdprConsent }; const bidRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(bidRequest.data).to.not.include.any.keys('consent_string'); }); it('should add the ttduid parameter if a bid request contains a value for Unified ID from The Trade Desk', function() { const bidRequest = spec.buildRequests(bidRequests)[0]; expect(bidRequest.data.ttduid).to.eq('fake-tdid'); }); it('should add the pubcid parameter if a bid request contains a value for the Publisher Common ID Module in the' + ' userId object of the bidrequest', function() { const bidRequest = spec.buildRequests(bidRequests)[0]; expect(bidRequest.data.pubcid).to.eq('fake-pubcid'); }); it('should add the pubcid parameter if a bid request contains a value for the Publisher Common ID Module in the' + ' crumbs object of the bidrequest', function() { const bidData = utils.deepClone(bidRequests); delete bidData[0].userId.pubcid; const bidRequest = spec.buildRequests(bidData)[0]; expect(bidRequest.data.pubcid).to.eq('fake-pubcid-in-crumbs-obj'); }); it('should add the pubcid parameter if a bid request contains a value for the Publisher Common ID Module in the' + ' crumbs object of the bidrequest', function() { const bidRequest = spec.buildRequests(bidRequests)[0]; delete bidRequest.userId; expect(bidRequest.data.pubcid).to.eq('fake-pubcid'); }); it('should add the idluid parameter if a bid request contains a value for Identity Link from Live Ramp', function() { const bidRequest = spec.buildRequests(bidRequests)[0]; expect(bidRequest.data.idluid).to.eq('fake-identity-link'); }); it('should add the id5uid parameter if a bid request contains a value for ID5', function() { const bidRequest = spec.buildRequests(bidRequests)[0]; expect(bidRequest.data.id5uid.id).to.eq('fake-id5id'); expect(bidRequest.data.id5uid.linkType).to.eq(2); }); it('should add the liuid parameter if a bid request contains a value for LiveIntent ID', function() { const bidRequest = spec.buildRequests(bidRequests)[0]; expect(bidRequest.data.liuid).to.eq('fake-lipbid'); }); it('should add Sharethrough specific parameters', function() { const builtBidRequests = spec.buildRequests(bidRequests); expect(builtBidRequests[0]).to.deep.include({ strData: { skipIframeBusting: undefined, iframeSize: undefined, sizes: [[600, 300]] } }); }); it('should add a supply chain parameter if schain is present', function() { // shallow copy of the first bidRequest obj, so we don't mutate const bidRequest = Object.assign({}, bidRequests[0]); bidRequest['schain'] = { ver: '1.0', complete: 1, nodes: [ { asi: 'directseller.com', sid: '00001', rid: 'BidRequest1', hp: 1 } ] }; const builtBidRequest = spec.buildRequests([bidRequest])[0]; expect(builtBidRequest.data.schain).to.eq(JSON.stringify(bidRequest.schain)); }); describe('gpid', () => { it('should include the gpid param if pbadslot is found in ortb2Imp in the bid request', () => { const bidRequest = spec.buildRequests(bidRequests)[0]; expect(bidRequest.data.gpid).to.eq('adslot-id-1') }); it('should not include the gpid param if pbadslot is not found in ortb2Imp in the bid request', () => { const bidRequest = spec.buildRequests(bidRequests)[1]; expect(bidRequest.data).to.not.include.any.keys('gpid'); }); }); it('should add badv if provided', () => { const builtBidRequest = spec.buildRequests([bidRequests[3]])[0]; expect(builtBidRequest.data.badv).to.have.members(['domain1.com', 'domain2.com']) }); it('should add bcat if provided', () => { const builtBidRequest = spec.buildRequests([bidRequests[4]])[0]; expect(builtBidRequest.data.bcat).to.have.members(['IAB1-1', 'IAB1-2']) }); it('should not add a supply chain parameter if schain is missing', function() { const bidRequest = spec.buildRequests(bidRequests)[0]; expect(bidRequest.data).to.not.include.any.keys('schain'); }); it('should include the bidfloor parameter if it is present in the bid request', function() { const bidRequest = Object.assign({}, bidRequests[0]); bidRequest['getFloor'] = () => ({ currency: 'USD', floor: 0.5 }); const builtBidRequest = spec.buildRequests([bidRequest])[0]; expect(builtBidRequest.data.bidfloor).to.eq(0.5); }); it('should not include the bidfloor parameter if it is missing in the bid request', function() { const bidRequest = Object.assign({}, bidRequests[0]); const builtBidRequest = spec.buildRequests([bidRequest])[0]; expect(builtBidRequest.data).to.not.include.any.keys('bidfloor'); }); describe('coppa', function() { it('should add coppa to request if enabled', function() { config.setConfig({coppa: true}); const bidRequest = Object.assign({}, bidRequests[0]); const builtBidRequest = spec.buildRequests([bidRequest])[0]; expect(builtBidRequest.data.coppa).to.eq(true); }); it('should not add coppa to request if disabled', function() { config.setConfig({coppa: false}); const bidRequest = Object.assign({}, bidRequests[0]); const builtBidRequest = spec.buildRequests([bidRequest])[0]; expect(builtBidRequest.data.coppa).to.be.undefined; }); it('should not add coppa to request if unknown value', function() { config.setConfig({coppa: 'something'}); const bidRequest = Object.assign({}, bidRequests[0]); const builtBidRequest = spec.buildRequests([bidRequest])[0]; expect(builtBidRequest.data.coppa).to.be.undefined; }); }); }); describe('.interpretResponse', function() { it('returns a correctly parsed out response', function() { expect(spec.interpretResponse(bidderResponse, prebidRequests[0])[0]).to.deep.include( { width: 1, height: 1, cpm: 12.34, creativeId: 'aCreativeId', dealId: 'aDealId', currency: 'USD', netRevenue: true, ttl: 360, meta: { advertiserDomains: [] } }); }); it('returns a correctly parsed out response with largest size when strData.skipIframeBusting is true', function() { expect(spec.interpretResponse(bidderResponse, prebidRequests[1])[0]).to.include( { width: 300, height: 300, cpm: 12.34, creativeId: 'aCreativeId', dealId: 'aDealId', currency: 'USD', netRevenue: true, ttl: 360 }); }); it('returns a correctly parsed out response with explicitly defined size when strData.skipIframeBusting is true and strData.iframeSize is provided', function() { expect(spec.interpretResponse(bidderResponse, prebidRequests[2])[0]).to.include( { width: 500, height: 500, cpm: 12.34, creativeId: 'aCreativeId', dealId: 'aDealId', currency: 'USD', netRevenue: true, ttl: 360 }); }); it('returns a correctly parsed out response with explicitly defined size when strData.skipIframeBusting is false and strData.sizes contains [0, 0] only', function() { expect(spec.interpretResponse(bidderResponse, prebidRequests[3])[0]).to.include( { width: 0, height: 0, cpm: 12.34, creativeId: 'aCreativeId', dealId: 'aDealId', currency: 'USD', netRevenue: true, ttl: 360 }); }); it('returns a correctly parsed out response with explicitly defined size when strData.skipIframeBusting is false and strData.sizes contains multiple sizes', function() { expect(spec.interpretResponse(bidderResponse, prebidRequests[4])[0]).to.include( { width: 300, height: 300, cpm: 12.34, creativeId: 'aCreativeId', dealId: 'aDealId', currency: 'USD', netRevenue: true, ttl: 360 }); }); it('returns a blank array if there are no creatives', function() { const bidResponse = { body: { creatives: [] } }; expect(spec.interpretResponse(bidResponse, prebidRequests[0])).to.be.an('array').that.is.empty; }); it('returns a blank array if body object is empty', function() { const bidResponse = { body: {} }; expect(spec.interpretResponse(bidResponse, prebidRequests[0])).to.be.an('array').that.is.empty; }); it('returns a blank array if body is null', function() { const bidResponse = { body: null }; expect(spec.interpretResponse(bidResponse, prebidRequests[0])).to.be.an('array').that.is.empty; }); it('correctly generates ad markup when skipIframeBusting is false', function() { const adMarkup = spec.interpretResponse(bidderResponse, prebidRequests[0])[0].ad; let resp = null; expect(() => btoa(JSON.stringify(bidderResponse))).to.throw(); expect(() => resp = sharethroughInternal.b64EncodeUnicode(JSON.stringify(bidderResponse))).not.to.throw(); expect(adMarkup).to.match( /data-str-native-key="pKey" data-stx-response-name="str_response_bidId"/); expect(!!adMarkup.indexOf(resp)).to.eql(true); // insert functionality to autodetect whether or not in safeframe, and handle JS insertion expect(adMarkup).to.match(/isLockedInFrame/); expect(adMarkup).to.match(/handleIframe/); }); it('correctly generates ad markup when skipIframeBusting is true', function() { const adMarkup = spec.interpretResponse(bidderResponse, prebidRequests[1])[0].ad; let resp = null; expect(() => btoa(JSON.stringify(bidderResponse))).to.throw(); expect(() => resp = sharethroughInternal.b64EncodeUnicode(JSON.stringify(bidderResponse))).not.to.throw(); expect(adMarkup).to.match( /data-str-native-key="pKey" data-stx-response-name="str_response_bidId"/); expect(!!adMarkup.indexOf(resp)).to.eql(true); expect(adMarkup).to.match( /<script src="https:\/\/native.sharethrough.com\/assets\/sfp.js"><\/script>/); }); }); describe('.getUserSyncs', function() { const cookieSyncs = ['cookieUrl1', 'cookieUrl2', 'cookieUrl3']; const serverResponses = [{ body: { cookieSyncUrls: cookieSyncs } }]; it('returns an array of correctly formatted user syncs', function() { const syncArray = spec.getUserSyncs({ pixelEnabled: true }, serverResponses, null, 'fake-privacy-signal'); expect(syncArray).to.deep.equal([ { type: 'image', url: 'cookieUrl1&us_privacy=fake-privacy-signal' }, { type: 'image', url: 'cookieUrl2&us_privacy=fake-privacy-signal' }, { type: 'image', url: 'cookieUrl3&us_privacy=fake-privacy-signal' }] ); }); it('returns an empty array if serverResponses is empty', function() { const syncArray = spec.getUserSyncs({ pixelEnabled: true }, []); expect(syncArray).to.be.an('array').that.is.empty; }); it('returns an empty array if the body is null', function() { const syncArray = spec.getUserSyncs({ pixelEnabled: true }, [{ body: null }]); expect(syncArray).to.be.an('array').that.is.empty; }); it('returns an empty array if the body.cookieSyncUrls is missing', function() { const syncArray = spec.getUserSyncs({ pixelEnabled: true }, [{ body: { creatives: ['creative'] } }]); expect(syncArray).to.be.an('array').that.is.empty; }); it('returns an empty array if pixels are not enabled', function() { const syncArray = spec.getUserSyncs({ pixelEnabled: false }, serverResponses); expect(syncArray).to.be.an('array').that.is.empty; }); }); });