diff --git a/modules/automatadAnalyticsAdapter.js b/modules/automatadAnalyticsAdapter.js new file mode 100644 index 00000000000..7d7bd8cb34c --- /dev/null +++ b/modules/automatadAnalyticsAdapter.js @@ -0,0 +1,325 @@ +import { + logError, + logInfo, + logMessage +} from '../src/utils.js'; + +import CONSTANTS from '../src/constants.json'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import { config } from '../src/config.js' + +/** Prebid Event Handlers */ + +const ADAPTER_CODE = 'automatadAnalytics' +const trialCountMilsMapping = [1500, 3000, 5000, 10000]; + +var isLoggingEnabled; var queuePointer = 0; var retryCount = 0; var timer = null; var __atmtdAnalyticsQueue = []; + +const prettyLog = (level, text, isGroup = false, cb = () => {}) => { + if (self.isLoggingEnabled === undefined) { + if (window.localStorage.getItem('__aggLoggingEnabled')) { + self.isLoggingEnabled = true + } else { + const queryParams = new URLSearchParams(new URL(window.location.href).search) + self.isLoggingEnabled = queryParams.has('aggLoggingEnabled') + } + } + + if (self.isLoggingEnabled) { + if (isGroup) { + logInfo(`ATD Analytics Adapter: ${level.toUpperCase()}: ${text} --- Group Start ---`) + try { + cb(); + } catch (error) { + logError(`ATD Analytics Adapter: ERROR: ${'Error during cb function in prettyLog'}`) + } + logInfo(`ATD Analytics Adapter: ${level.toUpperCase()}: ${text} --- Group End ---`) + } else { + logInfo(`ATD Analytics Adapter: ${level.toUpperCase()}: ${text}`) + } + } +} + +const processEvents = () => { + if (self.retryCount === trialCountMilsMapping.length) { + self.prettyLog('error', `Aggregator still hasn't loaded. Processing que stopped`, trialCountMilsMapping, self.retryCount) + return; + } + + self.prettyLog('status', `Que has been inactive for a while. Adapter starting to process que now... Trial Count = ${self.retryCount + 1}`) + + let shouldTryAgain = false + + while (self.queuePointer < self.__atmtdAnalyticsQueue.length) { + const eventType = self.__atmtdAnalyticsQueue[self.queuePointer][0] + const args = self.__atmtdAnalyticsQueue[self.queuePointer][1] + + try { + switch (eventType) { + case CONSTANTS.EVENTS.AUCTION_INIT: + if (window.atmtdAnalytics && window.atmtdAnalytics.auctionInitHandler) { + window.atmtdAnalytics.auctionInitHandler(args); + } else { + shouldTryAgain = true + } + break; + case CONSTANTS.EVENTS.BID_REQUESTED: + if (window.atmtdAnalytics && window.atmtdAnalytics.bidRequestedHandler) { + window.atmtdAnalytics.bidRequestedHandler(args); + } + break; + case CONSTANTS.EVENTS.BID_RESPONSE: + if (window.atmtdAnalytics && window.atmtdAnalytics.bidResponseHandler) { + window.atmtdAnalytics.bidResponseHandler(args); + } + break; + case CONSTANTS.EVENTS.BID_REJECTED: + if (window.atmtdAnalytics && window.atmtdAnalytics.bidRejectedHandler) { + window.atmtdAnalytics.bidRejectedHandler(args); + } + break; + case CONSTANTS.EVENTS.BIDDER_DONE: + if (window.atmtdAnalytics && window.atmtdAnalytics.bidderDoneHandler) { + window.atmtdAnalytics.bidderDoneHandler(args); + } + break; + case CONSTANTS.EVENTS.BID_WON: + if (window.atmtdAnalytics && window.atmtdAnalytics.bidWonHandler) { + window.atmtdAnalytics.bidWonHandler(args); + } + break; + case CONSTANTS.EVENTS.NO_BID: + if (window.atmtdAnalytics && window.atmtdAnalytics.noBidHandler) { + window.atmtdAnalytics.noBidHandler(args); + } + break; + case CONSTANTS.EVENTS.BID_TIMEOUT: + if (window.atmtdAnalytics && window.atmtdAnalytics.bidderTimeoutHandler) { + window.atmtdAnalytics.bidderTimeoutHandler(args); + } + break; + case CONSTANTS.EVENTS.AUCTION_DEBUG: + if (window.atmtdAnalytics && window.atmtdAnalytics.auctionDebugHandler) { + window.atmtdAnalytics.auctionDebugHandler(args); + } + break; + case 'slotRenderEnded': + if (window.atmtdAnalytics && window.atmtdAnalytics.slotRenderEndedGPTHandler) { + window.atmtdAnalytics.slotRenderEndedGPTHandler(args); + } else { + shouldTryAgain = true + } + break; + case 'impressionViewable': + if (window.atmtdAnalytics && window.atmtdAnalytics.impressionViewableHandler) { + window.atmtdAnalytics.impressionViewableHandler(args); + } else { + shouldTryAgain = true + } + break; + } + + if (shouldTryAgain) break; + } catch (error) { + self.prettyLog('error', `Unhandled Error while processing ${eventType} of ${self.queuePointer}th index in the que. Will not be retrying this raw event ...`, true, () => { + logError(`The error is `, error) + }) + } + + self.queuePointer = self.queuePointer + 1 + } + + if (shouldTryAgain) { + if (trialCountMilsMapping[self.retryCount]) self.prettyLog('warn', `Adapter failed to process event as aggregator has not loaded. Retrying in ${trialCountMilsMapping[self.retryCount]}ms ...`); + setTimeout(self.processEvents, trialCountMilsMapping[self.retryCount]) + self.retryCount = self.retryCount + 1 + } +} + +const addGPTHandlers = () => { + const googletag = window.googletag || {} + googletag.cmd = googletag.cmd || [] + googletag.cmd.push(() => { + googletag.pubads().addEventListener('slotRenderEnded', (event) => { + if (window.atmtdAnalytics && window.atmtdAnalytics.slotRenderEndedGPTHandler) { + if (window.__atmtdAggregatorFirstAuctionInitialized === true) { + window.atmtdAnalytics.slotRenderEndedGPTHandler(event) + return; + } + } + self.__atmtdAnalyticsQueue.push(['slotRenderEnded', event]) + self.prettyLog(`warn`, `Aggregator not initialised at auctionInit, exiting slotRenderEnded handler and pushing to que instead`) + }) + + googletag.pubads().addEventListener('impressionViewable', (event) => { + if (window.atmtdAnalytics && window.atmtdAnalytics.impressionViewableHandler) { + if (window.__atmtdAggregatorFirstAuctionInitialized === true) { + window.atmtdAnalytics.impressionViewableHandler(event) + return; + } + } + self.__atmtdAnalyticsQueue.push(['impressionViewable', event]) + self.prettyLog(`warn`, `Aggregator not initialised at auctionInit, exiting impressionViewable handler and pushing to que instead`) + }) + }) +} + +const initializeQueue = () => { + self.__atmtdAnalyticsQueue.push = (args) => { + Array.prototype.push.apply(self.__atmtdAnalyticsQueue, [args]); + if (timer) { + clearTimeout(timer); + timer = null; + } + + if (args[0] === CONSTANTS.EVENTS.AUCTION_INIT) { + const timeout = parseInt(config.getConfig('bidderTimeout')) + 1500 + timer = setTimeout(() => { + self.processEvents() + }, timeout); + } else { + timer = setTimeout(() => { + self.processEvents() + }, 1500); + } + }; +} + +// ANALYTICS ADAPTER + +let baseAdapter = adapter({analyticsType: 'bundle'}); +let atmtdAdapter = Object.assign({}, baseAdapter, { + + disableAnalytics() { + baseAdapter.disableAnalytics.apply(this, arguments); + }, + + track({eventType, args}) { + switch (eventType) { + case CONSTANTS.EVENTS.AUCTION_INIT: + if (window.atmtdAnalytics && window.atmtdAnalytics.auctionInitHandler) { + self.prettyLog('status', 'Aggregator loaded, initialising auction through handlers'); + window.atmtdAnalytics.auctionInitHandler(args); + } else { + self.prettyLog('warn', 'Aggregator not loaded, initialising auction through que ...'); + self.__atmtdAnalyticsQueue.push([eventType, args]) + } + break; + case CONSTANTS.EVENTS.BID_REQUESTED: + if (window.atmtdAnalytics && window.atmtdAnalytics.bidRequestedHandler) { + window.atmtdAnalytics.bidRequestedHandler(args); + } else { + self.prettyLog('warn', `Aggregator not loaded, pushing ${eventType} to que instead ...`); + self.__atmtdAnalyticsQueue.push([eventType, args]) + } + break; + case CONSTANTS.EVENTS.BID_REJECTED: + if (window.atmtdAnalytics && window.atmtdAnalytics.bidRejectedHandler) { + window.atmtdAnalytics.bidRejectedHandler(args); + } else { + self.prettyLog('warn', `Aggregator not loaded, pushing ${eventType} to que instead ...`); + self.__atmtdAnalyticsQueue.push([eventType, args]) + } + break; + case CONSTANTS.EVENTS.BID_RESPONSE: + if (window.atmtdAnalytics && window.atmtdAnalytics.bidResponseHandler) { + window.atmtdAnalytics.bidResponseHandler(args); + } else { + self.prettyLog('warn', `Aggregator not loaded, pushing ${eventType} to que instead ...`); + self.__atmtdAnalyticsQueue.push([eventType, args]) + } + break; + case CONSTANTS.EVENTS.BIDDER_DONE: + if (window.atmtdAnalytics && window.atmtdAnalytics.bidderDoneHandler) { + window.atmtdAnalytics.bidderDoneHandler(args); + } else { + self.prettyLog('warn', `Aggregator not loaded, pushing ${eventType} to que instead ...`); + self.__atmtdAnalyticsQueue.push([eventType, args]) + } + break; + case CONSTANTS.EVENTS.BID_WON: + if (window.atmtdAnalytics && window.atmtdAnalytics.bidWonHandler) { + window.atmtdAnalytics.bidWonHandler(args); + } else { + self.prettyLog('warn', `Aggregator not loaded, pushing ${eventType} to que instead ...`); + self.__atmtdAnalyticsQueue.push([eventType, args]) + } + break; + case CONSTANTS.EVENTS.NO_BID: + if (window.atmtdAnalytics && window.atmtdAnalytics.noBidHandler) { + window.atmtdAnalytics.noBidHandler(args); + } else { + self.prettyLog('warn', `Aggregator not loaded, pushing ${eventType} to que instead ...`); + self.__atmtdAnalyticsQueue.push([eventType, args]) + } + break; + case CONSTANTS.EVENTS.AUCTION_DEBUG: + if (window.atmtdAnalytics && window.atmtdAnalytics.auctionDebugHandler) { + window.atmtdAnalytics.auctionDebugHandler(args); + } else { + self.prettyLog('warn', `Aggregator not loaded, pushing ${eventType} to que instead ...`); + self.__atmtdAnalyticsQueue.push([eventType, args]) + } + break; + case CONSTANTS.EVENTS.BID_TIMEOUT: + if (window.atmtdAnalytics && window.atmtdAnalytics.bidderTimeoutHandler) { + window.atmtdAnalytics.bidderTimeoutHandler(args); + } else { + self.prettyLog('warn', `Aggregator not loaded, pushing ${eventType} to que instead ...`); + self.__atmtdAnalyticsQueue.push([eventType, args]) + } + break; + } + } +}); + +atmtdAdapter.originEnableAnalytics = atmtdAdapter.enableAnalytics + +atmtdAdapter.enableAnalytics = function (configuration) { + if ((configuration === undefined && typeof configuration !== 'object') || configuration.options === undefined) { + logError('A valid configuration must be passed to the Atmtd Analytics Adapter.'); + return; + } + + const conf = configuration.options + + if (conf === undefined || typeof conf !== 'object' || conf.siteID === undefined || conf.publisherID === undefined) { + logError('A valid publisher ID and siteID must be passed to the Atmtd Analytics Adapter.'); + return; + } + + self.initializeQueue() + self.addGPTHandlers() + + window.__atmtdSDKConfig = { + publisherID: conf.publisherID, + siteID: conf.siteID, + collectDebugMessages: conf.logDebug ? conf.logDebug : false + } + + logMessage(`Automatad Analytics Adapter enabled with sdk config`, window.__atmtdSDKConfig) + + // eslint-disable-next-line + atmtdAdapter.originEnableAnalytics(configuration) +}; + +/// /////////// ADAPTER REGISTRATION ////////////// + +adapterManager.registerAnalyticsAdapter({ + adapter: atmtdAdapter, + code: ADAPTER_CODE +}); + +export var self = { + __atmtdAnalyticsQueue, + processEvents, + initializeQueue, + addGPTHandlers, + prettyLog, + queuePointer, + retryCount, + isLoggingEnabled +} + +export default atmtdAdapter; diff --git a/modules/automatadAnalyticsAdapter.md b/modules/automatadAnalyticsAdapter.md new file mode 100644 index 00000000000..2be1af87f20 --- /dev/null +++ b/modules/automatadAnalyticsAdapter.md @@ -0,0 +1,23 @@ + +# Overview + +Module Name: Automatad Analytics Adapter +Module Type: Analytics Adapter +Maintainer: tech@automatad.com + +# Description + +Analytics adapter for automatad.com. Contact tech@automatad.com for information. + +# Test Parameters + +``` +{ + provider: 'automatadAnalytics', + options: { + publisherID: 'N8vZLx', + siteID: 'PXfvBq' + } +} + +``` \ No newline at end of file diff --git a/test/spec/modules/automatadAnalyticsAdapter_spec.js b/test/spec/modules/automatadAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..e591f7e8e95 --- /dev/null +++ b/test/spec/modules/automatadAnalyticsAdapter_spec.js @@ -0,0 +1,533 @@ +import * as events from 'src/events'; +import * as utils from 'src/utils.js'; + +import spec, {self as exports} from 'modules/automatadAnalyticsAdapter.js'; + +import CONSTANTS from 'src/constants.json'; +import { expect } from 'chai'; + +const { + AUCTION_DEBUG, + BID_REQUESTED, + BID_REJECTED, + AUCTION_INIT, + BIDDER_DONE, + BID_RESPONSE, + BID_TIMEOUT, + BID_WON, + NO_BID +} = CONSTANTS.EVENTS + +const CONFIG_WITH_DEBUG = { + provider: 'atmtdAnalyticsAdapter', + options: { + publisherID: '230', + siteID: '421' + }, + includeEvents: [AUCTION_DEBUG, AUCTION_INIT, BIDDER_DONE, BID_RESPONSE, BID_TIMEOUT, NO_BID, BID_WON, BID_REQUESTED, BID_REJECTED] +} + +describe('Automatad Analytics Adapter', () => { + var sandbox, clock; + + describe('Adapter Setup Configuration', () => { + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(utils, 'logMessage') + sandbox.stub(events, 'getEvents').returns([]); + sandbox.stub(utils, 'logError'); + }); + afterEach(() => { + sandbox.restore(); + }); + + it('Should log error and return false if nothing is passed as the param in the enable analytics call', () => { + spec.enableAnalytics() + + expect(utils.logError.called).to.equal(true) + }); + + it('Should log error and return false if object type is not passed as the param in the enable analytics call', () => { + spec.enableAnalytics('hello world') + + expect(utils.logError.called).to.equal(true) + }); + + it('Should log error and return false if options is not defined in the enable analytics call', () => { + spec.enableAnalytics({ + provider: 'atmtdAnalyticsAdapter' + }) + + expect(utils.logError.called).to.equal(true) + }); + it('Should log error and return false if pub id is not defined in the enable analytics call', () => { + spec.enableAnalytics({ + provider: 'atmtdAnalyticsAdapter', + options: { + siteID: '230' + } + }) + + expect(utils.logError.called).to.equal(true) + }); + it('Should log error and return false if pub id is not defined in the enable analytics call', () => { + spec.enableAnalytics({ + provider: 'atmtdAnalyticsAdapter', + options: { + publisherID: '230' + } + }) + + expect(utils.logError.called).to.equal(true) + }); + it('Should successfully configure the adapter and set global log debug messages flag to false', () => { + spec.enableAnalytics({ + provider: 'atmtdAnalyticsAdapter', + options: { + publisherID: '230', + siteID: '421', + logDebug: false + } + }); + expect(utils.logError.called).to.equal(false) + expect(utils.logMessage.called).to.equal(true) + spec.disableAnalytics(); + }); + it('Should successfully configure the adapter and set global log debug messages flag to true', () => { + sandbox.stub(exports, 'initializeQueue').callsFake(() => {}); + sandbox.stub(exports, 'addGPTHandlers').callsFake(() => {}); + const config = { + provider: 'atmtdAnalyticsAdapter', + options: { + publisherID: '230', + siteID: '410', + logDebug: true + } + } + + spec.enableAnalytics(config) + expect(utils.logError.called).to.equal(false) + expect(exports.initializeQueue.called).to.equal(true) + expect(exports.addGPTHandlers.called).to.equal(true) + expect(utils.logMessage.called).to.equal(true) + spec.disableAnalytics(); + }); + }); + + describe('Behaviour of the adapter when the sdk has loaded', () => { + before(() => { + spec.enableAnalytics(CONFIG_WITH_DEBUG); + const obj = { + auctionInitHandler: (args) => {}, + bidResponseHandler: (args) => {}, + bidderDoneHandler: (args) => {}, + bidWonHandler: (args) => {}, + noBidHandler: (args) => {}, + auctionDebugHandler: (args) => {}, + bidderTimeoutHandler: (args) => {}, + bidRequestedHandler: (args) => {}, + bidRejectedHandler: (args) => {} + } + + global.window.atmtdAnalytics = obj + + Object.keys(obj).forEach((fn) => sandbox.spy(global.window.atmtdAnalytics, fn)) + }) + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(events, 'getEvents').returns([]); + sandbox.stub(utils, 'logMessage'); + sandbox.stub(utils, 'logError'); + }); + afterEach(() => { + sandbox.restore(); + }); + after(() => { + global.window.atmtdAnalytics = undefined; + spec.disableAnalytics(); + }) + + it('Should call the auctionInitHandler when the auction init event is fired', () => { + events.emit(AUCTION_INIT, {type: AUCTION_INIT}) + expect(global.window.atmtdAnalytics.auctionInitHandler.called).to.equal(true) + }); + + it('Should call the bidRequested when the bidRequested event is fired', () => { + events.emit(BID_REQUESTED, {type: BID_REQUESTED}) + expect(global.window.atmtdAnalytics.bidRequestedHandler.called).to.equal(true) + }); + + it('Should call the bidRejected when the bidRejected event is fired', () => { + events.emit(BID_REJECTED, {type: BID_REJECTED}) + expect(global.window.atmtdAnalytics.bidRejectedHandler.called).to.equal(true) + }); + + it('Should call the bidResponseHandler when the bidResponse event is fired', () => { + events.emit(BID_RESPONSE, {type: BID_RESPONSE}) + expect(global.window.atmtdAnalytics.bidResponseHandler.called).to.equal(true) + }); + + it('Should call the bidderDoneHandler when the bidderDone event is fired', () => { + events.emit(BIDDER_DONE, {type: BIDDER_DONE}) + expect(global.window.atmtdAnalytics.bidderDoneHandler.called).to.equal(true) + }); + + it('Should call the bidWonHandler when the bidWon event is fired', () => { + events.emit(BID_WON, {type: BID_WON}) + expect(global.window.atmtdAnalytics.bidWonHandler.called).to.equal(true) + }); + + it('Should call the noBidHandler when the noBid event is fired', () => { + events.emit(NO_BID, {type: NO_BID}) + expect(global.window.atmtdAnalytics.noBidHandler.called).to.equal(true) + }); + + it('Should call the bidTimeoutHandler when the bidTimeout event is fired', () => { + events.emit(BID_TIMEOUT, {type: BID_TIMEOUT}) + expect(global.window.atmtdAnalytics.bidderTimeoutHandler.called).to.equal(true) + }); + + it('Should call the auctionDebugHandler when the auctionDebug event is fired', () => { + events.emit(AUCTION_DEBUG, {type: AUCTION_DEBUG}) + expect(global.window.atmtdAnalytics.auctionDebugHandler.called).to.equal(true) + }); + }); + + describe('Behaviour of the adapter when the SDK has not loaded', () => { + before(() => { + spec.enableAnalytics(CONFIG_WITH_DEBUG); + }) + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(events, 'getEvents').returns([]); + sandbox.stub(utils, 'logMessage'); + sandbox.stub(utils, 'logError'); + + global.window.atmtdAnalytics = undefined + exports.__atmtdAnalyticsQueue.length = 0 + sandbox.stub(exports.__atmtdAnalyticsQueue, 'push').callsFake((args) => { + Array.prototype.push.apply(exports.__atmtdAnalyticsQueue, [args]); + }) + }); + afterEach(() => { + sandbox.restore(); + }); + after(() => { + spec.disableAnalytics(); + }) + + it('Should push to the que when the auctionInit event is fired', () => { + events.emit(AUCTION_INIT, {type: AUCTION_INIT}) + expect(exports.__atmtdAnalyticsQueue.push.called).to.equal(true) + expect(exports.__atmtdAnalyticsQueue).to.be.an('array').to.have.lengthOf(1) + expect(exports.__atmtdAnalyticsQueue[0]).to.have.lengthOf(2) + expect(exports.__atmtdAnalyticsQueue[0][0]).to.equal(AUCTION_INIT) + expect(exports.__atmtdAnalyticsQueue[0][1].type).to.equal(AUCTION_INIT) + }); + + it('Should push to the que when the bidResponse event is fired', () => { + events.emit(BID_RESPONSE, {type: BID_RESPONSE}) + expect(exports.__atmtdAnalyticsQueue.push.called).to.equal(true) + expect(exports.__atmtdAnalyticsQueue).to.be.an('array').to.have.lengthOf(1) + expect(exports.__atmtdAnalyticsQueue[0]).to.have.lengthOf(2) + expect(exports.__atmtdAnalyticsQueue[0][0]).to.equal(BID_RESPONSE) + expect(exports.__atmtdAnalyticsQueue[0][1].type).to.equal(BID_RESPONSE) + }); + + it('Should push to the que when the bidRequested event is fired', () => { + events.emit(BID_REQUESTED, {type: BID_REQUESTED}) + expect(exports.__atmtdAnalyticsQueue.push.called).to.equal(true) + expect(exports.__atmtdAnalyticsQueue).to.be.an('array').to.have.lengthOf(1) + expect(exports.__atmtdAnalyticsQueue[0]).to.have.lengthOf(2) + expect(exports.__atmtdAnalyticsQueue[0][0]).to.equal(BID_REQUESTED) + expect(exports.__atmtdAnalyticsQueue[0][1].type).to.equal(BID_REQUESTED) + }); + + it('Should push to the que when the bidRejected event is fired', () => { + events.emit(BID_REJECTED, {type: BID_REJECTED}) + expect(exports.__atmtdAnalyticsQueue.push.called).to.equal(true) + expect(exports.__atmtdAnalyticsQueue).to.be.an('array').to.have.lengthOf(1) + expect(exports.__atmtdAnalyticsQueue[0]).to.have.lengthOf(2) + expect(exports.__atmtdAnalyticsQueue[0][0]).to.equal(BID_REJECTED) + expect(exports.__atmtdAnalyticsQueue[0][1].type).to.equal(BID_REJECTED) + }); + + it('Should push to the que when the bidderDone event is fired', () => { + events.emit(BIDDER_DONE, {type: BIDDER_DONE}) + expect(exports.__atmtdAnalyticsQueue.push.called).to.equal(true) + expect(exports.__atmtdAnalyticsQueue).to.be.an('array').to.have.lengthOf(1) + expect(exports.__atmtdAnalyticsQueue[0]).to.have.lengthOf(2) + expect(exports.__atmtdAnalyticsQueue[0][0]).to.equal(BIDDER_DONE) + expect(exports.__atmtdAnalyticsQueue[0][1].type).to.equal(BIDDER_DONE) + }); + + it('Should push to the que when the bidWon event is fired', () => { + events.emit(BID_WON, {type: BID_WON}) + expect(exports.__atmtdAnalyticsQueue.push.called).to.equal(true) + expect(exports.__atmtdAnalyticsQueue).to.be.an('array').to.have.lengthOf(1) + expect(exports.__atmtdAnalyticsQueue[0]).to.have.lengthOf(2) + expect(exports.__atmtdAnalyticsQueue[0][0]).to.equal(BID_WON) + expect(exports.__atmtdAnalyticsQueue[0][1].type).to.equal(BID_WON) + }); + + it('Should push to the que when the noBid event is fired', () => { + events.emit(NO_BID, {type: NO_BID}) + expect(exports.__atmtdAnalyticsQueue.push.called).to.equal(true) + expect(exports.__atmtdAnalyticsQueue).to.be.an('array').to.have.lengthOf(1) + expect(exports.__atmtdAnalyticsQueue[0]).to.have.lengthOf(2) + expect(exports.__atmtdAnalyticsQueue[0][0]).to.equal(NO_BID) + expect(exports.__atmtdAnalyticsQueue[0][1].type).to.equal(NO_BID) + }); + + it('Should push to the que when the auctionDebug is fired', () => { + events.emit(AUCTION_DEBUG, {type: AUCTION_DEBUG}) + expect(exports.__atmtdAnalyticsQueue.push.called).to.equal(true) + expect(exports.__atmtdAnalyticsQueue).to.be.an('array').to.have.lengthOf(1) + expect(exports.__atmtdAnalyticsQueue[0]).to.have.lengthOf(2) + expect(exports.__atmtdAnalyticsQueue[0][0]).to.equal(AUCTION_DEBUG) + expect(exports.__atmtdAnalyticsQueue[0][1].type).to.equal(AUCTION_DEBUG) + }); + + it('Should push to the que when the bidderTimeout event is fired', () => { + events.emit(BID_TIMEOUT, {type: BID_TIMEOUT}) + expect(exports.__atmtdAnalyticsQueue.push.called).to.equal(true) + expect(exports.__atmtdAnalyticsQueue).to.be.an('array').to.have.lengthOf(1) + expect(exports.__atmtdAnalyticsQueue[0]).to.have.lengthOf(2) + expect(exports.__atmtdAnalyticsQueue[0][0]).to.equal(BID_TIMEOUT) + expect(exports.__atmtdAnalyticsQueue[0][1].type).to.equal(BID_TIMEOUT) + }); + }); + + describe('Process Events from Que when SDK still has not loaded', () => { + before(() => { + spec.enableAnalytics({ + provider: 'atmtdAnalyticsAdapter', + options: { + publisherID: '230', + siteID: '421' + } + }); + global.window.atmtdAnalytics = undefined + + sandbox.stub(exports.__atmtdAnalyticsQueue, 'push').callsFake((args) => { + Array.prototype.push.apply(exports.__atmtdAnalyticsQueue, [args]); + }) + }) + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(events, 'getEvents').returns([]); + sandbox.spy(exports, 'prettyLog') + sandbox.spy(exports, 'processEvents') + + clock = sandbox.useFakeTimers(); + exports.__atmtdAnalyticsQueue.length = 0 + }); + afterEach(() => { + sandbox.restore(); + exports.queuePointer = 0; + exports.retryCount = 0; + exports.__atmtdAnalyticsQueue = [] + spec.disableAnalytics(); + }) + + it('Should retry processing auctionInit in certain intervals', () => { + expect(exports.queuePointer).to.equal(0) + expect(exports.retryCount).to.equal(0) + const que = [[AUCTION_INIT, {type: AUCTION_INIT}]] + exports.__atmtdAnalyticsQueue.push(que[0]) + exports.processEvents() + expect(exports.prettyLog.getCall(0).args[0]).to.equal('status') + expect(exports.prettyLog.getCall(0).args[1]).to.equal(`Que has been inactive for a while. Adapter starting to process que now... Trial Count = 1`) + expect(exports.prettyLog.getCall(1).args[0]).to.equal('warn') + expect(exports.prettyLog.getCall(1).args[1]).to.equal(`Adapter failed to process event as aggregator has not loaded. Retrying in 1500ms ...`) + clock.tick(1510) + expect(exports.prettyLog.getCall(2).args[0]).to.equal('status') + expect(exports.prettyLog.getCall(2).args[1]).to.equal(`Que has been inactive for a while. Adapter starting to process que now... Trial Count = 2`) + expect(exports.prettyLog.getCall(3).args[0]).to.equal('warn') + expect(exports.prettyLog.getCall(3).args[1]).to.equal(`Adapter failed to process event as aggregator has not loaded. Retrying in 3000ms ...`) + clock.tick(3010) + expect(exports.prettyLog.getCall(4).args[0]).to.equal('status') + expect(exports.prettyLog.getCall(4).args[1]).to.equal(`Que has been inactive for a while. Adapter starting to process que now... Trial Count = 3`) + expect(exports.prettyLog.getCall(5).args[0]).to.equal('warn') + expect(exports.prettyLog.getCall(5).args[1]).to.equal(`Adapter failed to process event as aggregator has not loaded. Retrying in 5000ms ...`) + clock.tick(5010) + expect(exports.prettyLog.getCall(6).args[0]).to.equal('status') + expect(exports.prettyLog.getCall(6).args[1]).to.equal(`Que has been inactive for a while. Adapter starting to process que now... Trial Count = 4`) + expect(exports.prettyLog.getCall(7).args[0]).to.equal('warn') + expect(exports.prettyLog.getCall(7).args[1]).to.equal(`Adapter failed to process event as aggregator has not loaded. Retrying in 10000ms ...`) + clock.tick(10010) + expect(exports.prettyLog.getCall(8).args[0]).to.equal('error') + expect(exports.prettyLog.getCall(8).args[1]).to.equal(`Aggregator still hasn't loaded. Processing que stopped`) + expect(exports.queuePointer).to.equal(0) + expect(exports.processEvents.callCount).to.equal(5) + }) + + it('Should retry processing slotRenderEnded in certain intervals', () => { + expect(exports.queuePointer).to.equal(0) + expect(exports.retryCount).to.equal(0) + const que = [['slotRenderEnded', {type: 'slotRenderEnded'}]] + exports.__atmtdAnalyticsQueue.push(que[0]) + exports.processEvents() + expect(exports.prettyLog.getCall(0).args[0]).to.equal('status') + expect(exports.prettyLog.getCall(0).args[1]).to.equal(`Que has been inactive for a while. Adapter starting to process que now... Trial Count = 1`) + expect(exports.prettyLog.getCall(1).args[0]).to.equal('warn') + expect(exports.prettyLog.getCall(1).args[1]).to.equal(`Adapter failed to process event as aggregator has not loaded. Retrying in 1500ms ...`) + clock.tick(1510) + expect(exports.prettyLog.getCall(2).args[0]).to.equal('status') + expect(exports.prettyLog.getCall(2).args[1]).to.equal(`Que has been inactive for a while. Adapter starting to process que now... Trial Count = 2`) + expect(exports.prettyLog.getCall(3).args[0]).to.equal('warn') + expect(exports.prettyLog.getCall(3).args[1]).to.equal(`Adapter failed to process event as aggregator has not loaded. Retrying in 3000ms ...`) + clock.tick(3010) + expect(exports.prettyLog.getCall(4).args[0]).to.equal('status') + expect(exports.prettyLog.getCall(4).args[1]).to.equal(`Que has been inactive for a while. Adapter starting to process que now... Trial Count = 3`) + expect(exports.prettyLog.getCall(5).args[0]).to.equal('warn') + expect(exports.prettyLog.getCall(5).args[1]).to.equal(`Adapter failed to process event as aggregator has not loaded. Retrying in 5000ms ...`) + clock.tick(5010) + expect(exports.prettyLog.getCall(6).args[0]).to.equal('status') + expect(exports.prettyLog.getCall(6).args[1]).to.equal(`Que has been inactive for a while. Adapter starting to process que now... Trial Count = 4`) + expect(exports.prettyLog.getCall(7).args[0]).to.equal('warn') + expect(exports.prettyLog.getCall(7).args[1]).to.equal(`Adapter failed to process event as aggregator has not loaded. Retrying in 10000ms ...`) + clock.tick(10010) + expect(exports.prettyLog.getCall(8).args[0]).to.equal('error') + expect(exports.prettyLog.getCall(8).args[1]).to.equal(`Aggregator still hasn't loaded. Processing que stopped`) + expect(exports.queuePointer).to.equal(0) + expect(exports.processEvents.callCount).to.equal(5) + }) + + it('Should retry processing impressionViewable in certain intervals', () => { + expect(exports.queuePointer).to.equal(0) + expect(exports.retryCount).to.equal(0) + const que = [['impressionViewable', {type: 'impressionViewable'}]] + exports.__atmtdAnalyticsQueue.push(que[0]) + exports.processEvents() + expect(exports.prettyLog.getCall(0).args[0]).to.equal('status') + expect(exports.prettyLog.getCall(0).args[1]).to.equal(`Que has been inactive for a while. Adapter starting to process que now... Trial Count = 1`) + expect(exports.prettyLog.getCall(1).args[0]).to.equal('warn') + expect(exports.prettyLog.getCall(1).args[1]).to.equal(`Adapter failed to process event as aggregator has not loaded. Retrying in 1500ms ...`) + clock.tick(1510) + expect(exports.prettyLog.getCall(2).args[0]).to.equal('status') + expect(exports.prettyLog.getCall(2).args[1]).to.equal(`Que has been inactive for a while. Adapter starting to process que now... Trial Count = 2`) + expect(exports.prettyLog.getCall(3).args[0]).to.equal('warn') + expect(exports.prettyLog.getCall(3).args[1]).to.equal(`Adapter failed to process event as aggregator has not loaded. Retrying in 3000ms ...`) + clock.tick(3010) + expect(exports.prettyLog.getCall(4).args[0]).to.equal('status') + expect(exports.prettyLog.getCall(4).args[1]).to.equal(`Que has been inactive for a while. Adapter starting to process que now... Trial Count = 3`) + expect(exports.prettyLog.getCall(5).args[0]).to.equal('warn') + expect(exports.prettyLog.getCall(5).args[1]).to.equal(`Adapter failed to process event as aggregator has not loaded. Retrying in 5000ms ...`) + clock.tick(5010) + expect(exports.prettyLog.getCall(6).args[0]).to.equal('status') + expect(exports.prettyLog.getCall(6).args[1]).to.equal(`Que has been inactive for a while. Adapter starting to process que now... Trial Count = 4`) + expect(exports.prettyLog.getCall(7).args[0]).to.equal('warn') + expect(exports.prettyLog.getCall(7).args[1]).to.equal(`Adapter failed to process event as aggregator has not loaded. Retrying in 10000ms ...`) + clock.tick(10010) + expect(exports.prettyLog.getCall(8).args[0]).to.equal('error') + expect(exports.prettyLog.getCall(8).args[1]).to.equal(`Aggregator still hasn't loaded. Processing que stopped`) + expect(exports.queuePointer).to.equal(0) + expect(exports.processEvents.callCount).to.equal(5) + }) + }); + + describe('Process Events from Que when SDK has loaded', () => { + before(() => { + spec.enableAnalytics({ + provider: 'atmtdAnalyticsAdapter', + options: { + publisherID: '230', + siteID: '421' + } + }); + sandbox = sinon.createSandbox(); + sandbox.reset() + const obj = { + auctionInitHandler: (args) => {}, + bidResponseHandler: (args) => {}, + bidderDoneHandler: (args) => {}, + bidWonHandler: (args) => {}, + noBidHandler: (args) => {}, + auctionDebugHandler: (args) => {}, + bidderTimeoutHandler: (args) => {}, + impressionViewableHandler: (args) => {}, + slotRenderEndedGPTHandler: (args) => {}, + bidRequestedHandler: (args) => {}, + bidRejectedHandler: (args) => {} + } + + global.window.atmtdAnalytics = obj; + + Object.keys(obj).forEach((fn) => sandbox.spy(global.window.atmtdAnalytics, fn)) + sandbox.stub(events, 'getEvents').returns([]); + sandbox.spy(exports, 'prettyLog') + exports.retryCount = 0; + exports.queuePointer = 0; + exports.__atmtdAnalyticsQueue = [ + [AUCTION_INIT, {type: AUCTION_INIT}], + [BID_RESPONSE, {type: BID_RESPONSE}], + [BID_REQUESTED, {type: BID_REQUESTED}], + [BID_REJECTED, {type: BID_REJECTED}], + [NO_BID, {type: NO_BID}], + [BID_WON, {type: BID_WON}], + [BIDDER_DONE, {type: BIDDER_DONE}], + [AUCTION_DEBUG, {type: AUCTION_DEBUG}], + [BID_TIMEOUT, {type: BID_TIMEOUT}], + ['slotRenderEnded', {type: 'slotRenderEnded'}], + ['impressionViewable', {type: 'impressionViewable'}] + ] + }); + after(() => { + sandbox.restore(); + spec.disableAnalytics(); + }) + + it('Should make calls to appropriate SDK event handlers', () => { + exports.processEvents() + expect(exports.prettyLog.getCall(0).args[0]).to.equal('status') + expect(exports.prettyLog.getCall(0).args[1]).to.equal(`Que has been inactive for a while. Adapter starting to process que now... Trial Count = 1`) + expect(exports.retryCount).to.equal(0) + expect(exports.prettyLog.callCount).to.equal(1) + expect(exports.queuePointer).to.equal(exports.__atmtdAnalyticsQueue.length) + expect(global.window.atmtdAnalytics.auctionInitHandler.calledOnce).to.equal(true) + expect(global.window.atmtdAnalytics.bidResponseHandler.calledOnce).to.equal(true) + expect(global.window.atmtdAnalytics.bidRejectedHandler.calledOnce).to.equal(true) + expect(global.window.atmtdAnalytics.bidRequestedHandler.calledOnce).to.equal(true) + expect(global.window.atmtdAnalytics.noBidHandler.calledOnce).to.equal(true) + expect(global.window.atmtdAnalytics.bidWonHandler.calledOnce).to.equal(true) + expect(global.window.atmtdAnalytics.auctionDebugHandler.calledOnce).to.equal(true) + expect(global.window.atmtdAnalytics.bidderTimeoutHandler.calledOnce).to.equal(true) + expect(global.window.atmtdAnalytics.bidderDoneHandler.calledOnce).to.equal(true) + expect(global.window.atmtdAnalytics.slotRenderEndedGPTHandler.calledOnce).to.equal(true) + expect(global.window.atmtdAnalytics.impressionViewableHandler.calledOnce).to.equal(true) + }) + }); + + describe('Prettylog fn tests', () => { + beforeEach(() => { + sandbox = sinon.createSandbox() + sandbox.spy(utils, 'logInfo') + sandbox.spy(utils, 'logError') + exports.isLoggingEnabled = true + }) + + afterEach(() => { + sandbox.restore() + }) + + it('Should call logMessage once in normal mode', () => { + exports.prettyLog('status', 'Hello world') + expect(utils.logInfo.callCount).to.equal(1) + }) + + it('Should call logMessage twice in group mode and have the cb called', () => { + const spy = sandbox.spy() + exports.prettyLog('status', 'Hello world', true, spy) + expect(utils.logInfo.callCount).to.equal(2) + expect(spy.called).to.equal(true) + }) + + it('Should call logMessage twice in group mode and have the cb which throws an error', () => { + const spy = sandbox.stub().throws() + exports.prettyLog('status', 'Hello world', true, spy) + expect(utils.logInfo.callCount).to.equal(2) + expect(utils.logError.called).to.equal(true) + }) + }); +});