UNPKG

mk9-prebid

Version:

Header Bidding Management Library

706 lines (651 loc) 23.2 kB
import { appierAnalyticsAdapter, getCpmInUsd, parseBidderCode, parseAdUnitCode, ANALYTICS_VERSION, BIDDER_STATUS } from 'modules/appierAnalyticsAdapter.js'; import {expect} from 'chai'; const events = require('src/events'); const constants = require('src/constants.json'); const affiliateId = 'WhctHaViHtI'; const configId = 'd9cc9a9be9b240eda17cf1c9a8a4b29c'; const serverUrl = 'https://analytics.server.url/v1'; const autoPick = 'none'; const predictionId = '2a91ca5de54a4a2e89950af439f7a27f'; const auctionId = 'b0b39610-b941-4659-a87c-de9f62d3e13e'; describe('Appier Prebid AnalyticsAdapter Testing', function () { describe('event tracking and message cache manager', function () { beforeEach(function () { const configOptions = { affiliateId: affiliateId, configId: configId, server: serverUrl, autoPick: autoPick, predictionId: predictionId, sampling: 0, adSampling: 1, }; appierAnalyticsAdapter.enableAnalytics({ provider: 'appierAnalytics', options: configOptions }); }); afterEach(function () { appierAnalyticsAdapter.disableAnalytics(); }); describe('#getCpmInUsd()', function() { it('should get bid cpm as currency is USD', function() { const receivedBids = [ { auctionId: auctionId, adUnitCode: 'adunit_1', bidder: 'appier', bidderCode: 'APPIER', requestId: 'a1b2c3d4', timeToRespond: 72, cpm: 0.1, currency: 'USD', originalCpm: 0.1, originalCurrency: 'USD', ad: '<html>fake ad1</html>' }, ] const result = getCpmInUsd(receivedBids[0]); expect(result).to.equal(0.1); }); }); describe('#parseBidderCode()', function() { it('should get lower case bidder code from bidderCode field value', function() { const receivedBids = [ { auctionId: auctionId, adUnitCode: 'adunit_1', bidder: 'appier', bidderCode: 'APPIER', requestId: 'a1b2c3d4', timeToRespond: 72, cpm: 0.1, currency: 'USD', originalCpm: 0.1, originalCurrency: 'USD', ad: '<html>fake ad1</html>' }, ]; const result = parseBidderCode(receivedBids[0]); expect(result).to.equal('appier'); }); it('should get lower case bidder code from bidder field value as bidderCode field is missing', function() { const receivedBids = [ { auctionId: auctionId, adUnitCode: 'adunit_1', bidder: 'APPIER', bidderCode: '', requestId: 'a1b2c3d4', timeToRespond: 72, cpm: 0.1, currency: 'USD', originalCpm: 0.1, originalCurrency: 'USD', ad: '<html>fake ad1</html>' }, ]; const result = parseBidderCode(receivedBids[0]); expect(result).to.equal('appier'); }); }); describe('#parseAdUnitCode()', function() { it('should get lower case adUnit code from adUnitCode field value', function() { const receivedBids = [ { auctionId: auctionId, adUnitCode: 'ADUNIT', bidder: 'appier', bidderCode: 'APPIER', requestId: 'a1b2c3d4', timeToRespond: 72, cpm: 0.1, currency: 'USD', originalCpm: 0.1, originalCurrency: 'USD', ad: '<html>fake ad1</html>' }, ]; const result = parseAdUnitCode(receivedBids[0]); expect(result).to.equal('adunit'); }); }); describe('#getCachedAuction()', function() { const existing = {timeoutBids: [{}]}; appierAnalyticsAdapter.cachedAuctions['test_auction_id'] = existing; it('should get the existing cached object if it exists', function() { const result = appierAnalyticsAdapter.getCachedAuction('test_auction_id'); expect(result).to.equal(existing); }); it('should create a new object and store it in the cache on cache miss', function() { const result = appierAnalyticsAdapter.getCachedAuction('no_such_id'); expect(result).to.deep.include({ timeoutBids: [], }); }); }); describe('when formatting JSON payload sent to backend', function() { const receivedBids = [ { auctionId: auctionId, adUnitCode: 'adunit_1', bidder: 'appier', bidderCode: 'appier', requestId: 'a1b2c3d4', timeToRespond: 72, cpm: 0.1, currency: 'USD', originalCpm: 0.1, originalCurrency: 'USD', ad: '<html>fake ad1</html>' }, { auctionId: auctionId, adUnitCode: 'adunit_1', bidder: 'reippa', bidderCode: 'reippa', requestId: 'b2c3d4e5', timeToRespond: 100, cpm: 0.08, currency: 'USD', originalCpm: 0.08, originalCurrency: 'USD', ad: '<html>fake ad2</html>' }, { auctionId: auctionId, adUnitCode: 'adunit_2', bidder: 'appier', bidderCode: 'appier', requestId: 'c3d4e5f6', timeToRespond: 120, cpm: 0.09, currency: 'USD', originalCpm: 0.09, originalCurrency: 'USD', ad: '<html>fake ad3</html>' }, ]; const highestCpmBids = [ { auctionId: auctionId, adUnitCode: 'adunit_1', bidder: 'appier', bidderCode: 'appier', // No requestId timeToRespond: 72, cpm: 0.1, currency: 'USD', originalCpm: 0.1, originalCurrency: 'USD', ad: '<html>fake ad1</html>' } ]; const noBids = [ { auctionId: auctionId, adUnitCode: 'adunit_2', bidder: 'appier', bidderCode: 'appier', bidId: 'a1b2c3d4', } ]; const timeoutBids = [ { auctionId: auctionId, adUnitCode: 'adunit_2', bidder: 'reippa', bidderCode: 'reippa', bidId: '00123d4c', } ]; const withoutOriginalCpmBids = [ { auctionId: auctionId, adUnitCode: 'adunit_2', bidder: 'appier', bidderCode: 'appier', requestId: 'c3d4e5f6', timeToRespond: 120, cpm: 0.29, currency: 'USD', originalCpm: '', originalCurrency: 'USD', ad: '<html>fake ad3</html>' }, ]; const withoutOriginalCurrencyBids = [ { auctionId: auctionId, adUnitCode: 'adunit_2', bidder: 'appier', bidderCode: 'appier', requestId: 'c3d4e5f6', timeToRespond: 120, cpm: 0.09, currency: 'USD', originalCpm: 0.09, originalCurrency: '', ad: '<html>fake ad3</html>' }, ]; function assertHavingRequiredMessageFields(message) { expect(message).to.include({ version: ANALYTICS_VERSION, auctionId: auctionId, affiliateId: affiliateId, configId: configId, // referrer: window.location.href, sampling: 0, adSampling: 1, prebid: '$prebid.version$', // autoPick: 'manual', }); } describe('#createCommonMessage', function() { it('should correctly serialize some common fields', function() { const message = appierAnalyticsAdapter.createCommonMessage(auctionId); assertHavingRequiredMessageFields(message); }); }); describe('#serializeBidResponse', function() { it('should handle BID properly and serialize bid price related fields', function() { const result = appierAnalyticsAdapter.serializeBidResponse(receivedBids[0], BIDDER_STATUS.BID); expect(result).to.include({ prebidWon: false, isTimeout: false, status: BIDDER_STATUS.BID, time: 72, cpm: 0.1, currency: 'USD', originalCpm: 0.1, originalCurrency: 'USD', cpmUsd: 0.1, }); }); it('should handle NO_BID properly and set status to noBid', function() { const result = appierAnalyticsAdapter.serializeBidResponse(noBids[0], BIDDER_STATUS.NO_BID); expect(result).to.include({ prebidWon: false, isTimeout: false, status: BIDDER_STATUS.NO_BID, }); }); it('should handle BID_WON properly and serialize bid price related fields', function() { const result = appierAnalyticsAdapter.serializeBidResponse(receivedBids[0], BIDDER_STATUS.BID_WON); expect(result).to.include({ prebidWon: true, isTimeout: false, status: BIDDER_STATUS.BID_WON, time: 72, cpm: 0.1, currency: 'USD', originalCpm: 0.1, originalCurrency: 'USD', cpmUsd: 0.1, }); }); it('should handle TIMEOUT properly and set status to timeout and isTimeout to true', function() { const result = appierAnalyticsAdapter.serializeBidResponse(noBids[0], BIDDER_STATUS.TIMEOUT); expect(result).to.include({ prebidWon: false, isTimeout: true, status: BIDDER_STATUS.TIMEOUT, }); }); it('should handle BID_WON properly and fill originalCpm field with cpm in missing originalCpm case', function() { const result = appierAnalyticsAdapter.serializeBidResponse(withoutOriginalCpmBids[0], BIDDER_STATUS.BID_WON); expect(result).to.include({ prebidWon: true, isTimeout: false, status: BIDDER_STATUS.BID_WON, time: 120, cpm: 0.29, currency: 'USD', originalCpm: 0.29, originalCurrency: 'USD', cpmUsd: 0.29, }); }); it('should handle BID_WON properly and fill originalCurrency field with currency in missing originalCurrency case', function() { const result = appierAnalyticsAdapter.serializeBidResponse(withoutOriginalCurrencyBids[0], BIDDER_STATUS.BID_WON); expect(result).to.include({ prebidWon: true, isTimeout: false, status: BIDDER_STATUS.BID_WON, time: 120, cpm: 0.09, currency: 'USD', originalCpm: 0.09, originalCurrency: 'USD', cpmUsd: 0.09, }); }); }); describe('#addBidResponseToMessage()', function() { it('should add a bid response in the output message, grouped by adunit_id and bidder', function() { const message = { adUnits: {} }; appierAnalyticsAdapter.addBidResponseToMessage(message, noBids[0], BIDDER_STATUS.NO_BID); expect(message.adUnits).to.deep.include({ 'adunit_2': { 'appier': { prebidWon: false, isTimeout: false, status: BIDDER_STATUS.NO_BID, } } }); }); }); describe('#createBidMessage()', function() { it('should format auction message sent to the backend', function() { const args = { auctionId: auctionId, timestamp: 1234567890, timeout: 3000, auctionEnd: 1234567990, adUnitCodes: ['adunit_1', 'adunit_2'], bidsReceived: receivedBids, noBids: noBids }; const result = appierAnalyticsAdapter.createBidMessage(args, highestCpmBids, timeoutBids); assertHavingRequiredMessageFields(result); expect(result).to.deep.include({ auctionElapsed: 100, timeout: 3000, adUnits: { 'adunit_1': { 'appier': { prebidWon: true, isTimeout: false, status: BIDDER_STATUS.BID, time: 72, cpm: 0.1, currency: 'USD', originalCpm: 0.1, originalCurrency: 'USD', cpmUsd: 0.1, }, 'reippa': { prebidWon: false, isTimeout: false, status: BIDDER_STATUS.BID, time: 100, cpm: 0.08, currency: 'USD', originalCpm: 0.08, originalCurrency: 'USD', cpmUsd: 0.08, } }, 'adunit_2': { // this bid result exists in both bid and noBid arrays and should be treated as bid 'appier': { prebidWon: false, isTimeout: false, time: 120, cpm: 0.09, currency: 'USD', originalCpm: 0.09, originalCurrency: 'USD', cpmUsd: 0.09, status: BIDDER_STATUS.BID, }, 'reippa': { prebidWon: false, isTimeout: true, status: BIDDER_STATUS.TIMEOUT, } } } }); }); }); describe('#createImpressionMessage()', function() { it('should format message sent to the backend with the bid result', function() { const bid = receivedBids[0]; const result = appierAnalyticsAdapter.createImpressionMessage(bid); assertHavingRequiredMessageFields(result); expect(result.adUnits).to.deep.include({ 'adunit_1': { 'appier': { prebidWon: true, isTimeout: false, status: BIDDER_STATUS.BID_WON, time: 72, cpm: 0.1, currency: 'USD', originalCpm: 0.1, originalCurrency: 'USD', cpmUsd: 0.1, } } }); }); }); describe('#createCreativeMessage()', function() { it('should generate message sent to the backend with ad html grouped by adunit and bidder', function() { const result = appierAnalyticsAdapter.createCreativeMessage(auctionId, receivedBids); assertHavingRequiredMessageFields(result); expect(result.adUnits).to.deep.include({ 'adunit_1': { 'appier': { ad: '<html>fake ad1</html>' }, 'reippa': { ad: '<html>fake ad2</html>' }, }, 'adunit_2': { 'appier': { ad: '<html>fake ad3</html>' } } }); }); }); describe('#handleBidTimeout()', function() { it('should cached the timeout bid as BID_TIMEOUT event was triggered', function() { appierAnalyticsAdapter.cachedAuctions['test_timeout_auction_id'] = { 'timeoutBids': [] }; const args = [{ auctionId: 'test_timeout_auction_id', timestamp: 1234567890, timeout: 3000, auctionEnd: 1234567990, bidsReceived: receivedBids, noBids: noBids }]; appierAnalyticsAdapter.handleBidTimeout(args); const result = appierAnalyticsAdapter.getCachedAuction('test_timeout_auction_id'); expect(result).to.deep.include({ timeoutBids: [{ auctionId: 'test_timeout_auction_id', timestamp: 1234567890, timeout: 3000, auctionEnd: 1234567990, bidsReceived: receivedBids, noBids: noBids }] }); }); }); describe('#handleBidWon()', function() { it('should call createImpressionMessage once as BID_WON event was triggered', function() { sinon.spy(appierAnalyticsAdapter, 'createImpressionMessage'); const receivedBids = [ { auctionId: auctionId, adUnitCode: 'adunit_1', bidder: 'appier', bidderCode: 'appier', requestId: 'a1b2c3d4', timeToRespond: 72, cpm: 0.1, currency: 'USD', originalCpm: 0.1, originalCurrency: 'USD', ad: '<html>fake ad1</html>' }, ] appierAnalyticsAdapter.handleBidWon(receivedBids[0]); sinon.assert.callCount(appierAnalyticsAdapter.createImpressionMessage, 1); appierAnalyticsAdapter.createImpressionMessage.restore(); }); }); }); }); describe('Appier Analytics Adapter track handler ', function () { const configOptions = { affiliateId: affiliateId, configId: configId, server: serverUrl, autoPick: autoPick, sampling: 1, adSampling: 1, }; beforeEach(function () { sinon.stub(events, 'getEvents').returns([]); appierAnalyticsAdapter.enableAnalytics({ provider: 'appierAnalytics', options: configOptions }); }); afterEach(function () { appierAnalyticsAdapter.disableAnalytics(); events.getEvents.restore(); }); it('should call handleBidWon as BID_WON trigger event', function() { sinon.spy(appierAnalyticsAdapter, 'handleBidWon'); events.emit(constants.EVENTS.BID_WON, {}); sinon.assert.callCount(appierAnalyticsAdapter.handleBidWon, 1); appierAnalyticsAdapter.handleBidWon.restore(); }); it('should call handleBidTimeout as BID_TIMEOUT trigger event', function() { sinon.spy(appierAnalyticsAdapter, 'handleBidTimeout'); events.emit(constants.EVENTS.BID_TIMEOUT, {}); sinon.assert.callCount(appierAnalyticsAdapter.handleBidTimeout, 1); appierAnalyticsAdapter.handleBidTimeout.restore(); }); it('should call handleAuctionEnd as AUCTION_END trigger event', function() { sinon.spy(appierAnalyticsAdapter, 'handleAuctionEnd'); events.emit(constants.EVENTS.AUCTION_END, {}); sinon.assert.callCount(appierAnalyticsAdapter.handleAuctionEnd, 1); appierAnalyticsAdapter.handleAuctionEnd.restore(); }); it('should call createCreativeMessage as AUCTION_END trigger event in adSampled is true', function() { const configOptions = { options: { affiliateId: affiliateId, configId: configId, server: serverUrl, autoPick: autoPick, sampling: 1, adSampling: 1, adSampled: true, } }; const args = { auctionId: 'test_timeout_auction_id', timestamp: 1234567890, timeout: 3000, auctionEnd: 1234567990, }; appierAnalyticsAdapter.initConfig(configOptions); sinon.stub(appierAnalyticsAdapter, 'sendEventMessage').returns({}); sinon.stub(appierAnalyticsAdapter, 'createBidMessage').returns({}); sinon.spy(appierAnalyticsAdapter, 'createCreativeMessage'); events.emit(constants.EVENTS.AUCTION_END, args); sinon.assert.callCount(appierAnalyticsAdapter.createCreativeMessage, 1); appierAnalyticsAdapter.sendEventMessage.restore(); appierAnalyticsAdapter.createBidMessage.restore(); appierAnalyticsAdapter.createCreativeMessage.restore(); }); }); describe('enableAnalytics and config parser', function () { const configOptions = { affiliateId: affiliateId, configId: configId, server: serverUrl, autoPick: autoPick, sampling: 0, adSampling: 1, }; beforeEach(function () { appierAnalyticsAdapter.enableAnalytics({ provider: 'appierAnalytics', options: configOptions }); }); afterEach(function () { appierAnalyticsAdapter.disableAnalytics(); }); it('should parse config correctly with optional values', function () { expect(appierAnalyticsAdapter.getAnalyticsOptions().options).to.deep.equal(configOptions); expect(appierAnalyticsAdapter.getAnalyticsOptions().affiliateId).to.equal(configOptions.affiliateId); expect(appierAnalyticsAdapter.getAnalyticsOptions().configId).to.equal(configOptions.configId); expect(appierAnalyticsAdapter.getAnalyticsOptions().server).to.equal(configOptions.server); expect(appierAnalyticsAdapter.getAnalyticsOptions().autoPick).to.equal(configOptions.autoPick); expect(appierAnalyticsAdapter.getAnalyticsOptions().sampled).to.equal(false); expect(appierAnalyticsAdapter.getAnalyticsOptions().adSampled).to.equal(true); }); it('should not enable Analytics when affiliateId is missing', function () { const configOptions = { options: { configId: configId } }; const validConfig = appierAnalyticsAdapter.initConfig(configOptions); expect(validConfig).to.equal(false); }); it('should use DEFAULT_SERVER when server is missing', function () { const configOptions = { options: { configId: configId, affiliateId: affiliateId } }; appierAnalyticsAdapter.initConfig(configOptions); expect(appierAnalyticsAdapter.getAnalyticsOptions().server).to.equal('https://prebid-analytics.c.appier.net/v1'); }); it('should use null when autoPick is missing', function () { const configOptions = { options: { configId: configId, affiliateId: affiliateId } }; appierAnalyticsAdapter.initConfig(configOptions); expect(appierAnalyticsAdapter.getAnalyticsOptions().autoPick).to.equal(null); }); it('should not enable Analytics when configId is missing', function () { const configOptions = { options: { affiliateId: affiliateId } }; const validConfig = appierAnalyticsAdapter.initConfig(configOptions); expect(validConfig).to.equal(false); }); it('should fall back to default value when sampling factor is not number', function () { const configOptions = { options: { affiliateId: affiliateId, configId: configId, sampling: 'string', adSampling: 'string' } }; appierAnalyticsAdapter.enableAnalytics({ provider: 'appierAnalytics', options: configOptions }); expect(appierAnalyticsAdapter.getAnalyticsOptions().sampled).to.equal(false); expect(appierAnalyticsAdapter.getAnalyticsOptions().adSampled).to.equal(true); }); }); });