diff --git a/modules/sovrnAnalyticsAdapter.js b/modules/sovrnAnalyticsAdapter.js
new file mode 100644
index 00000000000..c996caeb400
--- /dev/null
+++ b/modules/sovrnAnalyticsAdapter.js
@@ -0,0 +1,325 @@
+import adapter from '../src/AnalyticsAdapter'
+import adaptermanager from '../src/adapterManager'
+import CONSTANTS from '../src/constants.json'
+import {ajaxBuilder} from '../src/ajax'
+import * as utils from '../src/utils'
+import {config} from '../src/config'
+import find from 'core-js/library/fn/array/find'
+import includes from 'core-js/library/fn/array/includes'
+
+const ajax = ajaxBuilder(0)
+
+const {
+ EVENTS: {
+ AUCTION_END,
+ BID_REQUESTED,
+ BID_ADJUSTMENT,
+ BID_RESPONSE,
+ BID_WON
+ }
+} = CONSTANTS
+
+let pbaUrl = 'https://pba.aws.lijit.com/analytics'
+let currentAuctions = {};
+const analyticsType = 'endpoint'
+
+const getClosestTop = () => {
+ let topFrame = window;
+ let err = false;
+ try {
+ while (topFrame.parent.document !== topFrame.document) {
+ if (topFrame.parent.document) {
+ topFrame = topFrame.parent;
+ } else {
+ throw new Error();
+ }
+ }
+ } catch (e) {
+ bException = true;
+ }
+
+ return {
+ topFrame,
+ err
+ };
+};
+
+const getBestPageUrl = ({err: crossDomainError, topFrame}) => {
+ let sBestPageUrl = '';
+
+ if (!crossDomainError) {
+ // easy case- we can get top frame location
+ sBestPageUrl = topFrame.location.href;
+ } else {
+ try {
+ try {
+ sBestPageUrl = window.top.location.href;
+ } catch (e) {
+ let aOrigins = window.location.ancestorOrigins;
+ sBestPageUrl = aOrigins[aOrigins.length - 1];
+ }
+ } catch (e) {
+ sBestPageUrl = topFrame.document.referrer;
+ }
+ }
+
+ return sBestPageUrl;
+};
+const rootURL = getBestPageUrl(getClosestTop())
+
+let sovrnAnalyticsAdapter = Object.assign(adapter({url: pbaUrl, analyticsType}), {
+ track({ eventType, args }) {
+ try {
+ if (eventType === BID_WON) {
+ new BidWinner(this.sovrnId, args).send();
+ return
+ }
+ if (args && args.auctionId && currentAuctions[args.auctionId] && currentAuctions[args.auctionId].status === 'complete') {
+ throw new Error('Event Received after Auction Close Auction Id ' + args.auctionId)
+ }
+ if (args && args.auctionId && currentAuctions[args.auctionId] === undefined) {
+ currentAuctions[args.auctionId] = new AuctionData(this.sovrnId, args.auctionId)
+ }
+ switch (eventType) {
+ case BID_REQUESTED:
+ console.log(args)
+ currentAuctions[args.auctionId].bidRequested(args)
+ break
+ case BID_ADJUSTMENT:
+ currentAuctions[args.auctionId].originalBid(args)
+ break
+ case BID_RESPONSE:
+ currentAuctions[args.auctionId].adjustedBid(args)
+ break
+ case AUCTION_END:
+ currentAuctions[args.auctionId].send();
+ break
+ }
+ } catch (e) {
+ new LogError(e, this.sovrnId, {eventType, args}).send()
+ }
+ },
+})
+
+sovrnAnalyticsAdapter.getAuctions = function () {
+ return currentAuctions;
+};
+
+sovrnAnalyticsAdapter.originEnableAnalytics = sovrnAnalyticsAdapter.enableAnalytics;
+
+// override enableAnalytics so we can get access to the config passed in from the page
+sovrnAnalyticsAdapter.enableAnalytics = function (config) {
+ let sovrnId = ''
+ if (config && config.options && (config.options.sovrnId || config.options.affiliateId)) {
+ sovrnId = config.options.sovrnId || config.options.affiliateId;
+ } else {
+ utils.logError('Need Sovrn Id to log auction results. Please contact a Sovrn representative if you do not know your Sovrn Id.')
+ return
+ }
+ sovrnAnalyticsAdapter.sovrnId = sovrnId;
+ if (config.options.pbaUrl) {
+ pbaUrl = config.options.pbaUrl;
+ }
+ sovrnAnalyticsAdapter.originEnableAnalytics(config) // call the base class function
+};
+
+adaptermanager.registerAnalyticsAdapter({
+ adapter: sovrnAnalyticsAdapter,
+ code: 'sovrn'
+});
+
+/** Class Representing a Winning Bid */
+class BidWinner {
+ /**
+ * Creates a new bid winner
+ * @param {string} sovrnId - the affiliate id from the analytics config
+ * @param {*} event - the args object from the auction event
+ */
+ constructor(sovrnId, event) {
+ this.body = {}
+ this.body.prebidVersion = $$REPO_AND_VERSION$$
+ this.body.sovrnId = sovrnId
+ this.body.winningBid = JSON.parse(JSON.stringify(event))
+ this.body.url = rootURL
+ this.body.payload = 'winner'
+ delete this.body.winningBid.ad
+ }
+
+ /**
+ * Sends the auction to the the ingest server
+ */
+ send() {
+ this.body.ts = utils.timestamp()
+ ajax(
+ pbaUrl,
+ null,
+ JSON.stringify(this.body),
+ {
+ contentType: 'application/json',
+ method: 'POST',
+ }
+ )
+ }
+}
+
+/** Class representing an Auction */
+class AuctionData {
+ /**
+ * Create a new auction data collector
+ * @param {string} sovrnId - the affiliate id from the analytics config
+ * @param {string} auctionId - the auction id from the auction event
+ */
+ constructor(sovrnId, auctionId) {
+ this.auction = {}
+ this.auction.prebidVersion = $$REPO_AND_VERSION$$
+ this.auction.sovrnId = sovrnId
+ this.auction.auctionId = auctionId
+ this.auction.payload = 'auction'
+ this.auction.timeouts = {
+ buffer: config.getConfig('timeoutBuffer'),
+ bidder: config.getConfig('bidderTimeout'),
+ }
+ this.auction.priceGranularity = config.getConfig('priceGranularity')
+ this.auction.url = rootURL
+ this.auction.requests = []
+ this.auction.unsynced = []
+ this.dropBidFields = ['auctionId', 'ad', 'requestId', 'bidderCode']
+
+ setTimeout(function(id) {
+ delete currentAuctions[id]
+ }, 300000, this.auction.auctionId)
+ }
+
+ /**
+ * Record a bid request event
+ * @param {*} event - the args object from the auction event
+ */
+ bidRequested(event) {
+ const eventCopy = JSON.parse(JSON.stringify(event))
+ delete eventCopy.doneCbCallCount
+ delete eventCopy.auctionId
+ this.auction.requests.push(eventCopy)
+ }
+
+ /**
+ * Finds the bid from the auction that the event is associated with
+ * @param {*} event - the args object from the auction event
+ * @return {*} - the bid
+ */
+ findBid(event) {
+ const bidder = find(this.auction.requests, r => (r.bidderCode === event.bidderCode))
+ if (!bidder) {
+ this.auction.unsynced.push(JSON.parse(JSON.stringify(event)))
+ }
+ let bid = find(bidder.bids, b => (b.bidId === event.requestId))
+
+ if (!bid) {
+ event.unmatched = true
+ bidder.bids.push(JSON.parse(JSON.stringify(event)))
+ }
+ return bid
+ }
+
+ /**
+ * Records the original bid before any adjustments have been made
+ * @param {*} event - the args object from the auction event
+ * NOTE: the bid adjustment occurs before the bid response
+ * the bid adjustment seems to be the bid ready to be adjusted
+ */
+ originalBid(event) {
+ let bid = this.findBid(event)
+ if (bid) {
+ Object.assign(bid, JSON.parse(JSON.stringify(event)))
+ this.dropBidFields.forEach((f) => delete bid[f])
+ }
+ }
+
+ /**
+ * Replaces original values with adjusted values and records the original values for changed values
+ * in bid.originalValues
+ * @param {*} event - the args object from the auction event
+ */
+ adjustedBid(event) {
+ let bid = this.findBid(event)
+ if (bid) {
+ bid.originalValues = Object.keys(event).reduce((o, k) => {
+ if (JSON.stringify(bid[k]) !== JSON.stringify(event[k]) && !includes(this.dropBidFields, k)) {
+ o[k] = bid[k]
+ bid[k] = event[k]
+ }
+ return o
+ }, {})
+ }
+ }
+
+ /**
+ * Sends the auction to the the ingest server
+ */
+ send() {
+ let maxBids = {}
+ this.auction.requests.forEach(request => {
+ request.bids.forEach(bid => {
+ maxBids[bid.adUnitCode] = maxBids[bid.adUnitCode] || {cpm: 0}
+ if (bid.cpm > maxBids[bid.adUnitCode].cpm) {
+ maxBids[bid.adUnitCode] = bid
+ }
+ })
+ })
+ Object.keys(maxBids).forEach(unit => {
+ maxBids[unit].isAuctionWinner = true
+ })
+ this.auction.ts = utils.timestamp()
+ ajax(
+ pbaUrl,
+ () => {
+ currentAuctions[this.auction.auctionId] = {status: 'complete', auctionId: this.auction.auctionId}
+ },
+ JSON.stringify(this.auction),
+ {
+ contentType: 'application/json',
+ method: 'POST',
+ }
+ )
+ }
+}
+class LogError {
+ constructor(e, sovrnId, data) {
+ this.error = {}
+ this.error.payload = 'error'
+ this.error.message = e.message
+ this.error.stack = e.stack
+ this.error.data = data
+ this.error.prebidVersion = $$REPO_AND_VERSION$$
+ this.error.sovrnId = sovrnId
+ this.error.url = rootURL
+ this.error.userAgent = navigator.userAgent
+ }
+ send() {
+ if (this.error.data && this.error.data.requests) {
+ this.error.data.requests.forEach(request => {
+ if (request.bids) {
+ request.bids.forEach(bid => {
+ if (bid.ad) {
+ delete bid.ad
+ }
+ })
+ }
+ })
+ }
+ if (ErrorEvent.data && error.data.ad) {
+ delete error.data.ad
+ }
+ this.error.ts = utils.timestamp()
+ ajax(
+ pbaUrl,
+ null,
+ JSON.stringify(this.error),
+ {
+ contentType: 'application/json',
+ method: 'POST',
+ }
+ )
+ }
+}
+
+export default sovrnAnalyticsAdapter;
diff --git a/test/spec/modules/sovrnAnalyticsAdapter_spec.js b/test/spec/modules/sovrnAnalyticsAdapter_spec.js
new file mode 100644
index 00000000000..299e22ca790
--- /dev/null
+++ b/test/spec/modules/sovrnAnalyticsAdapter_spec.js
@@ -0,0 +1,546 @@
+import sovrnAnalyticsAdapter from '../../../modules/sovrnAnalyticsAdapter'
+import { expect } from 'chai'
+import {config} from 'src/config'
+import adaptermanager from 'src/adapterManager'
+var assert = require('assert');
+
+let events = require('src/events');
+let constants = require('src/constants.json');
+
+/**
+ * Emit analytics events
+ * @param {array} eventArr - array of objects to define the events that will fire
+ * @param {object} eventObj - key is eventType, value is event
+ * @param {string} auctionId - the auction id to attached to the events
+ */
+function emitEvent(eventType, event, auctionId) {
+ event.auctionId = auctionId;
+ events.emit(constants.EVENTS[eventType], event);
+}
+
+let auctionStartTimestamp = Date.now();
+let timeout = 3000;
+let auctionInit = {
+ timestamp: auctionStartTimestamp,
+ timeout: timeout
+};
+let bidderCode = 'sovrn';
+let bidderRequestId = '123bri';
+let adUnitCode = 'div';
+let adUnitCode2 = 'div2';
+let bidId = 'bidid';
+let bidId2 = 'bidid2';
+let tId = '7aafa3ee-a80a-46d7-a4a0-cbcba463d97a';
+let tId2 = '99dca3ee-a80a-46d7-a4a0-cbcba463d97e';
+let bidRequested = {
+ auctionStart: auctionStartTimestamp,
+ bidderCode: bidderCode,
+ bidderRequestId: bidderRequestId,
+ bids: [
+ {
+ adUnitCode: adUnitCode,
+ bidId: bidId,
+ bidder: bidderCode,
+ bidderRequestId: '10340af0c7dc72',
+ sizes: [[300, 250]],
+ startTime: auctionStartTimestamp + 100,
+ transactionId: tId
+ },
+ {
+ adUnitCode: adUnitCode2,
+ bidId: bidId2,
+ bidder: bidderCode,
+ bidderRequestId: '10340af0c7dc72',
+ sizes: [[300, 250]],
+ startTime: auctionStartTimestamp + 100,
+ transactionId: tId2
+ }
+ ],
+ doneCbCallCount: 1,
+ start: auctionStartTimestamp,
+ timeout: timeout
+};
+let bidResponse = {
+ bidderCode: bidderCode,
+ width: 300,
+ height: 250,
+ statusMessage: 'Bid available',
+ adId: '3870e27a5752fb',
+ mediaType: 'banner',
+ source: 'client',
+ requestId: bidId,
+ cpm: 0.8584999918937682,
+ creativeId: 'cridprebidrtb',
+ dealId: null,
+ currency: 'USD',
+ netRevenue: true,
+ ad: '
divvy mcdiv
',
+ ttl: 60000,
+ responseTimestamp: auctionStartTimestamp + 150,
+ requestTimestamp: auctionStartTimestamp + 100,
+ bidder: bidderCode,
+ adUnitCode: adUnitCode,
+ timeToRespond: 50,
+ pbLg: '0.50',
+ pbMg: '0.80',
+ pbHg: '0.85',
+ pbAg: '0.85',
+ pbDg: '0.85',
+ pbCg: '',
+ size: '300x250',
+ adserverTargeting: {
+ hb_bidder: bidderCode,
+ hb_adid: '3870e27a5752fb',
+ hb_pb: '0.85'
+ },
+ status: 'rendered'
+};
+
+let bidResponse2 = {
+ bidderCode: bidderCode,
+ width: 300,
+ height: 250,
+ statusMessage: 'Bid available',
+ adId: '9999e27a5752fb',
+ mediaType: 'banner',
+ source: 'client',
+ requestId: bidId2,
+ cpm: 0.12,
+ creativeId: 'cridprebidrtb',
+ dealId: null,
+ currency: 'USD',
+ netRevenue: true,
+ ad: 'divvy mcdiv
',
+ ttl: 60000,
+ responseTimestamp: auctionStartTimestamp + 150,
+ requestTimestamp: auctionStartTimestamp + 100,
+ bidder: bidderCode,
+ adUnitCode: adUnitCode2,
+ timeToRespond: 50,
+ pbLg: '0.10',
+ pbMg: '0.10',
+ pbHg: '0.10',
+ pbAg: '0.10',
+ pbDg: '0.10',
+ pbCg: '',
+ size: '300x250',
+ adserverTargeting: {
+ hb_bidder: bidderCode,
+ hb_adid: '9999e27a5752fb',
+ hb_pb: '0.10'
+ },
+ status: 'rendered'
+};
+let bidAdjustment = {};
+for (var k in bidResponse) bidAdjustment[k] = bidResponse[k];
+bidAdjustment.cpm = 0.8;
+let bidAdjustmentNoMatchingRequest = {
+ bidderCode: 'not-sovrn',
+ width: 300,
+ height: 250,
+ statusMessage: 'Bid available',
+ adId: '1',
+ mediaType: 'banner',
+ source: 'client',
+ requestId: '1',
+ cpm: 0.10,
+ creativeId: '',
+ dealId: null,
+ currency: 'USD',
+ netRevenue: true,
+ ad: 'divvy mcdiv
',
+ ttl: 60000,
+ responseTimestamp: auctionStartTimestamp + 150,
+ requestTimestamp: auctionStartTimestamp + 100,
+ bidder: 'not-sovrn',
+ adUnitCode: '',
+ timeToRespond: 50,
+ pbLg: '0.00',
+ pbMg: '0.10',
+ pbHg: '0.10',
+ pbAg: '0.10',
+ pbDg: '0.10',
+ pbCg: '',
+ size: '300x250',
+ adserverTargeting: {
+ hb_bidder: 'not-sovrn',
+ hb_adid: '1',
+ hb_pb: '0.10'
+ },
+};
+let bidResponseNoMatchingRequest = bidAdjustmentNoMatchingRequest;
+
+describe('Sovrn Analytics Adapter', function () {
+ let xhr;
+ let requests;
+ beforeEach(() => {
+ xhr = sinon.useFakeXMLHttpRequest();
+ xhr.onCreate = request => requests.push(request);
+ requests = [];
+ sinon.stub(events, 'getEvents').returns([]);
+ });
+ afterEach(() => {
+ xhr.restore();
+ events.getEvents.restore();
+ });
+
+ describe('enableAnalytics ', function () {
+ beforeEach(() => {
+ sinon.spy(sovrnAnalyticsAdapter, 'track');
+ });
+ afterEach(() => {
+ sovrnAnalyticsAdapter.disableAnalytics();
+ sovrnAnalyticsAdapter.track.restore();
+ });
+
+ it('should catch all events if affiliate id present', function () {
+ adaptermanager.enableAnalytics({
+ provider: 'sovrn',
+ options: {
+ sovrnId: 123
+ }
+ });
+
+ events.emit(constants.EVENTS.AUCTION_INIT, {});
+ events.emit(constants.EVENTS.AUCTION_END, {});
+ events.emit(constants.EVENTS.BID_REQUESTED, {});
+ events.emit(constants.EVENTS.BID_RESPONSE, {});
+ events.emit(constants.EVENTS.BID_WON, {});
+
+ sinon.assert.callCount(sovrnAnalyticsAdapter.track, 5);
+ });
+
+ it('should catch no events if no affiliate id', function () {
+ adaptermanager.enableAnalytics({
+ provider: 'sovrn',
+ options: {
+ }
+ });
+
+ events.emit(constants.EVENTS.AUCTION_INIT, {});
+ events.emit(constants.EVENTS.AUCTION_END, {});
+ events.emit(constants.EVENTS.BID_REQUESTED, {});
+ events.emit(constants.EVENTS.BID_RESPONSE, {});
+ events.emit(constants.EVENTS.BID_WON, {});
+
+ sinon.assert.callCount(sovrnAnalyticsAdapter.track, 0);
+ });
+ });
+
+ describe('sovrnAnalyticsAdapter ', function() {
+ beforeEach(() => {
+ sovrnAnalyticsAdapter.enableAnalytics({
+ provider: 'sovrn',
+ options: {
+ sovrnId: 123
+ }
+ });
+ sinon.spy(sovrnAnalyticsAdapter, 'track');
+ });
+ afterEach(() => {
+ sovrnAnalyticsAdapter.disableAnalytics();
+ sovrnAnalyticsAdapter.track.restore();
+ });
+ it('should have correct type', function () {
+ assert.equal(sovrnAnalyticsAdapter.getAdapterType(), 'endpoint')
+ })
+ });
+
+ describe('auction data collector ', function() {
+ beforeEach(() => {
+ sovrnAnalyticsAdapter.enableAnalytics({
+ provider: 'sovrn',
+ options: {
+ sovrnId: 123
+ }
+ });
+ sinon.spy(sovrnAnalyticsAdapter, 'track');
+ });
+ afterEach(() => {
+ sovrnAnalyticsAdapter.disableAnalytics();
+ sovrnAnalyticsAdapter.track.restore();
+ });
+ it('should create auctiondata record from init ', function () {
+ let auctionId = '123.123.123.123';
+ emitEvent('AUCTION_INIT', auctionInit, auctionId);
+
+ let auctionData = sovrnAnalyticsAdapter.getAuctions();
+ let currentAuction = auctionData[auctionId];
+ assert(currentAuction);
+ let expectedTimeOutData = {
+ buffer: config.getConfig('timeoutBuffer'),
+ bidder: config.getConfig('bidderTimeout'),
+ };
+ expect(currentAuction.auction.timeouts).to.deep.equal(expectedTimeOutData);
+ assert.equal(currentAuction.auction.payload, 'auction');
+ assert.equal(currentAuction.auction.priceGranularity, config.getConfig('priceGranularity'))
+ assert.equal(currentAuction.auction.auctionId, auctionId);
+ assert.equal(currentAuction.auction.sovrnId, 123);
+ });
+ it('should create a bidrequest object ', function() {
+ let auctionId = '234.234.234.234';
+ emitEvent('AUCTION_INIT', auctionInit, auctionId);
+ emitEvent('BID_REQUESTED', bidRequested, auctionId);
+
+ let auctionData = sovrnAnalyticsAdapter.getAuctions();
+ let currentAuction = auctionData[auctionId];
+ assert(currentAuction);
+ let requests = currentAuction.auction.requests;
+ assert(requests);
+ assert.equal(requests.length, 1);
+ assert.equal(requests[0].bidderCode, bidderCode);
+ assert.equal(requests[0].bidderRequestId, bidderRequestId);
+ assert.equal(requests[0].timeout, timeout);
+ let bids = requests[0].bids;
+ assert(bids);
+ assert.equal(bids.length, 2);
+ assert.equal(bids[0].bidId, bidId);
+ assert.equal(bids[0].bidder, bidderCode);
+ assert.equal(bids[0].transactionId, tId);
+ assert.equal(bids[0].sizes.length, 1);
+ assert.equal(bids[0].sizes[0][0], 300);
+ assert.equal(bids[0].sizes[0][1], 250);
+ expect(requests[0]).to.not.have.property('doneCbCallCount');
+ expect(requests[0]).to.not.have.property('auctionId');
+ });
+ it('should add results to the bid with response ', function () {
+ let auctionId = '345.345.345.345';
+ emitEvent('AUCTION_INIT', auctionInit, auctionId);
+ emitEvent('BID_REQUESTED', bidRequested, auctionId);
+ emitEvent('BID_RESPONSE', bidResponse, auctionId);
+
+ let auctionData = sovrnAnalyticsAdapter.getAuctions();
+ let currentAuction = auctionData[auctionId];
+ let returnedBid = currentAuction.auction.requests[0].bids[0];
+ assert.equal(returnedBid.bidId, bidId);
+ assert.equal(returnedBid.bidder, bidderCode);
+ assert.equal(returnedBid.transactionId, tId);
+ assert.equal(returnedBid.sizes.length, 1);
+ assert.equal(returnedBid.sizes[0][0], 300);
+ assert.equal(returnedBid.sizes[0][1], 250);
+ assert.equal(returnedBid.adserverTargeting.hb_adid, '3870e27a5752fb');
+ assert.equal(returnedBid.adserverTargeting.hb_bidder, bidderCode);
+ assert.equal(returnedBid.adserverTargeting.hb_pb, '0.85');
+ assert.equal(returnedBid.cpm, 0.8584999918937682);
+ });
+ it('should add new unsynced bid if no request exists for response ', function () {
+ let auctionId = '456.456.456.456';
+ emitEvent('AUCTION_INIT', auctionInit, auctionId);
+ emitEvent('BID_REQUESTED', bidRequested, auctionId);
+ emitEvent('BID_RESPONSE', bidResponseNoMatchingRequest, auctionId);
+
+ let auctionData = sovrnAnalyticsAdapter.getAuctions();
+ let currentAuction = auctionData[auctionId];
+ let requests = currentAuction.auction.requests;
+ assert(requests);
+ assert.equal(requests.length, 1);
+ let bidRequest = requests[0].bids[0];
+ expect(bidRequest).to.not.have.property('adserverTargeting');
+ expect(bidRequest).to.not.have.property('cpm');
+ expect(currentAuction.auction.unsynced[0]).to.deep.equal(bidResponseNoMatchingRequest);
+ });
+ it('should adjust the bid ', function () {
+ let auctionId = '567.567.567.567';
+ emitEvent('AUCTION_INIT', auctionInit, auctionId);
+ emitEvent('BID_REQUESTED', bidRequested, auctionId);
+ emitEvent('BID_ADJUSTMENT', bidResponse, auctionId);
+ emitEvent('BID_RESPONSE', bidAdjustment, auctionId);
+
+ let auctionData = sovrnAnalyticsAdapter.getAuctions();
+ let currentAuction = auctionData[auctionId];
+ let returnedBid = currentAuction.auction.requests[0].bids[0];
+ assert.equal(returnedBid.cpm, 0.8);
+ assert.equal(returnedBid.originalValues.cpm, 0.8584999918937682);
+ });
+ });
+ describe('auction data send ', function() {
+ let expectedPostBody = {
+ sovrnId: 123,
+ auctionId: '678.678.678.678',
+ payload: 'auction',
+ priceGranularity: 'medium',
+ };
+ let expectedRequests = {
+ bidderCode: 'sovrn',
+ bidderRequestId: '123bri',
+ timeout: 3000
+ };
+ let expectedBids = {
+ adUnitCode: 'div',
+ bidId: 'bidid',
+ bidder: 'sovrn',
+ bidderRequestId: '10340af0c7dc72',
+ transactionId: '7aafa3ee-a80a-46d7-a4a0-cbcba463d97a',
+ width: 300,
+ height: 250,
+ statusMessage: 'Bid available',
+ adId: '3870e27a5752fb',
+ mediaType: 'banner',
+ source: 'client',
+ cpm: 0.8584999918937682,
+ creativeId: 'cridprebidrtb',
+ dealId: null,
+ currency: 'USD',
+ netRevenue: true,
+ ttl: 60000,
+ timeToRespond: 50,
+ size: '300x250',
+ status: 'rendered',
+ isAuctionWinner: true
+ };
+ let SecondAdUnitExpectedBids = {
+ adUnitCode: 'div2',
+ bidId: 'bidid2',
+ bidder: 'sovrn',
+ bidderRequestId: '10340af0c7dc72',
+ transactionId: '99dca3ee-a80a-46d7-a4a0-cbcba463d97e',
+ width: 300,
+ height: 250,
+ statusMessage: 'Bid available',
+ adId: '9999e27a5752fb',
+ mediaType: 'banner',
+ source: 'client',
+ cpm: 0.12,
+ creativeId: 'cridprebidrtb',
+ dealId: null,
+ currency: 'USD',
+ netRevenue: true,
+ ttl: 60000,
+ timeToRespond: 50,
+ size: '300x250',
+ status: 'rendered',
+ isAuctionWinner: true
+ };
+ let expectedAdServerTargeting = {
+ hb_bidder: 'sovrn',
+ hb_adid: '3870e27a5752fb',
+ hb_pb: '0.85'
+ };
+ beforeEach(() => {
+ sovrnAnalyticsAdapter.enableAnalytics({
+ provider: 'sovrn',
+ options: {
+ sovrnId: 123
+ }
+ });
+ sinon.spy(sovrnAnalyticsAdapter, 'track');
+ });
+ afterEach(() => {
+ sovrnAnalyticsAdapter.disableAnalytics();
+ sovrnAnalyticsAdapter.track.restore();
+ });
+ it('should send auction data ', function () {
+ let auctionId = '678.678.678.678';
+ emitEvent('AUCTION_INIT', auctionInit, auctionId);
+ emitEvent('BID_REQUESTED', bidRequested, auctionId);
+ emitEvent('BID_RESPONSE', bidResponse, auctionId);
+ emitEvent('BID_RESPONSE', bidResponse2, auctionId)
+ emitEvent('AUCTION_END', {}, auctionId);
+ let requestBody = JSON.parse(requests[0].requestBody);
+ let requestsFromRequestBody = requestBody.requests[0];
+ let bidsFromRequests = requestsFromRequestBody.bids[0];
+ expect(requestBody).to.deep.include(expectedPostBody);
+ expect(requestBody.timeouts).to.deep.equal({buffer: 400, bidder: 3000});
+ expect(requestsFromRequestBody).to.deep.include(expectedRequests);
+ expect(bidsFromRequests).to.deep.include(expectedBids);
+ let bidsFromRequests2 = requestsFromRequestBody.bids[1];
+ expect(bidsFromRequests2).to.deep.include(SecondAdUnitExpectedBids);
+ expect(bidsFromRequests.adserverTargeting).to.deep.include(expectedAdServerTargeting);
+ });
+ });
+ describe('bid won data send ', function() {
+ let auctionId = '789.789.789.789';
+ let creativeId = 'cridprebidrtb';
+ let requestId = 'requestId69';
+ let bidWonEvent = {
+ ad: 'html',
+ adId: 'adId',
+ adUnitCode: adUnitCode,
+ auctionId: auctionId,
+ bidder: bidderCode,
+ bidderCode: bidderCode,
+ cpm: 1.01,
+ creativeId: creativeId,
+ currency: 'USD',
+ height: 250,
+ mediaType: 'banner',
+ requestId: requestId,
+ size: '300x250',
+ source: 'client',
+ status: 'rendered',
+ statusMessage: 'Bid available',
+ timeToRespond: 421,
+ ttl: 60,
+ width: 300
+ };
+ let expectedBidWonBody = {
+ sovrnId: 123,
+ payload: 'winner'
+ };
+ let expectedWinningBid = {
+ bidderCode: bidderCode,
+ width: 300,
+ height: 250,
+ statusMessage: 'Bid available',
+ adId: 'adId',
+ mediaType: 'banner',
+ source: 'client',
+ requestId: requestId,
+ cpm: 1.01,
+ creativeId: creativeId,
+ currency: 'USD',
+ ttl: 60,
+ auctionId: auctionId,
+ bidder: bidderCode,
+ adUnitCode: adUnitCode,
+ timeToRespond: 421,
+ size: '300x250',
+ };
+ beforeEach(() => {
+ sovrnAnalyticsAdapter.enableAnalytics({
+ provider: 'sovrn',
+ options: {
+ sovrnId: 123
+ }
+ });
+ sinon.spy(sovrnAnalyticsAdapter, 'track');
+ });
+ afterEach(() => {
+ sovrnAnalyticsAdapter.disableAnalytics();
+ sovrnAnalyticsAdapter.track.restore();
+ });
+ it('should send bid won data ', function () {
+ emitEvent('AUCTION_INIT', auctionInit, auctionId);
+ emitEvent('BID_WON', bidWonEvent, auctionId);
+ let requestBody = JSON.parse(requests[0].requestBody);
+ expect(requestBody).to.deep.include(expectedBidWonBody);
+ expect(requestBody.winningBid).to.deep.include(expectedWinningBid);
+ });
+ });
+ describe('Error Tracking', function() {
+ beforeEach(() => {
+ sovrnAnalyticsAdapter.enableAnalytics({
+ provider: 'sovrn',
+ options: {
+ sovrnId: 123
+ }
+ });
+ sinon.spy(sovrnAnalyticsAdapter, 'track');
+ });
+ afterEach(() => {
+ sovrnAnalyticsAdapter.disableAnalytics()
+ sovrnAnalyticsAdapter.track.restore()
+ });
+ it('should send an error message when a bid is received for a closed auction', function() {
+ let auctionId = '678.678.678.678';
+ emitEvent('AUCTION_INIT', auctionInit, auctionId)
+ emitEvent('BID_REQUESTED', bidRequested, auctionId)
+ emitEvent('AUCTION_END', {}, auctionId)
+ requests[0].respond(200)
+ emitEvent('BID_RESPONSE', bidResponse, auctionId)
+ let requestBody = JSON.parse(requests[1].requestBody)
+ expect(requestBody.payload).to.equal('error')
+ expect(requestBody.message).to.include('Event Received after Auction Close Auction Id')
+ })
+ })
+})