From efb5898995d19919b42cfcdfec36be6d8f80880b Mon Sep 17 00:00:00 2001
From: bjorn-lw <32431346+bjorn-lw@users.noreply.github.com>
Date: Tue, 9 Oct 2018 16:50:54 +0200
Subject: [PATCH] Livewrapped bid and analytics adapter (#3157)
* Livewrapped bid and analytics adapter
* Fixed some tests for browser compatibility
* Fixed some tests for browser compatibility
* Changed analytics adapter code name
* Fix double quote in debug message
---
modules/livewrappedAnalyticsAdapter.js | 222 +++++++
modules/livewrappedBidAdapter.js | 187 ++++++
modules/livewrappedBidAdapter.md | 28 +
.../livewrappedAnalyticsAdapter_spec.js | 265 ++++++++
.../modules/livewrappedBidAdapter_spec.js | 609 ++++++++++++++++++
5 files changed, 1311 insertions(+)
create mode 100644 modules/livewrappedAnalyticsAdapter.js
create mode 100644 modules/livewrappedBidAdapter.js
create mode 100644 modules/livewrappedBidAdapter.md
create mode 100644 test/spec/modules/livewrappedAnalyticsAdapter_spec.js
create mode 100644 test/spec/modules/livewrappedBidAdapter_spec.js
diff --git a/modules/livewrappedAnalyticsAdapter.js b/modules/livewrappedAnalyticsAdapter.js
new file mode 100644
index 00000000000..21c0bc73ec4
--- /dev/null
+++ b/modules/livewrappedAnalyticsAdapter.js
@@ -0,0 +1,222 @@
+import * as utils from 'src/utils';
+import {ajax} from 'src/ajax';
+import adapter from 'src/AnalyticsAdapter';
+import CONSTANTS from 'src/constants.json';
+import adaptermanager from 'src/adaptermanager';
+
+const ANALYTICSTYPE = 'endpoint';
+const URL = '//lwadm.com/analytics/10';
+const EMPTYURL = '';
+const REQUESTSENT = 1;
+const RESPONSESENT = 2;
+const WINSENT = 4;
+const TIMEOUTSENT = 8;
+
+let initOptions;
+export const BID_WON_TIMEOUT = 500;
+
+const cache = {
+ auctions: {},
+};
+
+let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE}), {
+ track({eventType, args}) {
+ utils.logInfo('LIVEWRAPPED_EVENT:', [eventType, args]);
+
+ switch (eventType) {
+ case CONSTANTS.EVENTS.AUCTION_INIT:
+ utils.logInfo('LIVEWRAPPED_AUCTION_INIT:', args);
+ cache.auctions[args.auctionId] = {bids: {}};
+ break;
+ case CONSTANTS.EVENTS.BID_REQUESTED:
+ utils.logInfo('LIVEWRAPPED_BID_REQUESTED:', args);
+
+ args.bids.forEach(function(bidRequest) {
+ cache.auctions[args.auctionId].timeStamp = args.start;
+ cache.auctions[args.auctionId].bids[bidRequest.bidId] = {
+ bidder: bidRequest.bidder,
+ adUnit: bidRequest.adUnitCode,
+ isBid: false,
+ won: false,
+ timeout: false,
+ sendStatus: 0
+ }
+
+ utils.logInfo(bidRequest);
+ })
+ utils.logInfo(livewrappedAnalyticsAdapter.requestEvents);
+ break;
+ case CONSTANTS.EVENTS.BID_RESPONSE:
+ utils.logInfo('LIVEWRAPPED_BID_RESPONSE:', args);
+
+ let bidResponse = cache.auctions[args.auctionId].bids[args.adId];
+ bidResponse.isBid = args.getStatusCode() === CONSTANTS.STATUS.GOOD;
+ bidResponse.width = args.width;
+ bidResponse.height = args.height;
+ bidResponse.cpm = args.cpm;
+ bidResponse.ttr = args.timeToRespond;
+ break;
+ case CONSTANTS.EVENTS.BIDDER_DONE:
+ utils.logInfo('LIVEWRAPPED_BIDDER_DONE:', args);
+ args.bids.forEach(doneBid => {
+ let bid = cache.auctions[doneBid.auctionId].bids[doneBid.bidId];
+ if (!bid.ttr) {
+ bid.ttr = Date.now() - args.auctionStart;
+ }
+ });
+ break;
+ case CONSTANTS.EVENTS.BID_WON:
+ utils.logInfo('LIVEWRAPPED_BID_WON:', args);
+ let wonBid = cache.auctions[args.auctionId].bids[args.adId];
+ wonBid.won = true;
+ if (wonBid.sendStatus != 0) {
+ livewrappedAnalyticsAdapter.sendEvents();
+ }
+ break;
+ case CONSTANTS.EVENTS.BID_TIMEOUT:
+ utils.logInfo('LIVEWRAPPED_BID_TIMEOUT:', args);
+ args.forEach(timeout => {
+ cache.auctions[timeout.auctionId].bids[timeout.bidId].timeout = true;
+ });
+ break;
+ case CONSTANTS.EVENTS.AUCTION_END:
+ utils.logInfo('LIVEWRAPPED_AUCTION_END:', args);
+ setTimeout(() => {
+ livewrappedAnalyticsAdapter.sendEvents();
+ }, BID_WON_TIMEOUT);
+ break;
+ }
+ }
+});
+
+// save the base class function
+livewrappedAnalyticsAdapter.originEnableAnalytics = livewrappedAnalyticsAdapter.enableAnalytics;
+livewrappedAnalyticsAdapter.allRequestEvents = [];
+
+// override enableAnalytics so we can get access to the config passed in from the page
+livewrappedAnalyticsAdapter.enableAnalytics = function (config) {
+ initOptions = config.options;
+ livewrappedAnalyticsAdapter.originEnableAnalytics(config);
+};
+
+livewrappedAnalyticsAdapter.sendEvents = function() {
+ var events = {
+ publisherId: initOptions.publisherId,
+ requests: getSentRequests(),
+ responses: getResponses(),
+ wins: getWins(),
+ timeouts: getTimeouts()
+ };
+
+ if (events.requests.length == 0 &&
+ events.responses.length == 0 &&
+ events.wins.length == 0 &&
+ events.timeouts.length == 0) {
+ return;
+ }
+
+ ajax(URL, undefined, JSON.stringify(events), {method: 'POST'});
+}
+
+function getSentRequests() {
+ var sentRequests = [];
+
+ Object.keys(cache.auctions).forEach(auctionId => {
+ Object.keys(cache.auctions[auctionId].bids).forEach(bidId => {
+ let auction = cache.auctions[auctionId];
+ let bid = auction.bids[bidId];
+ if (!(bid.sendStatus & REQUESTSENT)) {
+ bid.sendStatus |= REQUESTSENT;
+
+ sentRequests.push({
+ timeStamp: auction.timeStamp,
+ adUnit: bid.adUnit,
+ bidder: bid.bidder
+ });
+ }
+ });
+ });
+
+ return sentRequests;
+}
+
+function getResponses() {
+ var responses = [];
+
+ Object.keys(cache.auctions).forEach(auctionId => {
+ Object.keys(cache.auctions[auctionId].bids).forEach(bidId => {
+ let auction = cache.auctions[auctionId];
+ let bid = auction.bids[bidId];
+ if (!(bid.sendStatus & RESPONSESENT) && !bid.timeout) {
+ bid.sendStatus |= RESPONSESENT;
+
+ responses.push({
+ timeStamp: auction.timeStamp,
+ adUnit: bid.adUnit,
+ bidder: bid.bidder,
+ width: bid.width,
+ height: bid.height,
+ cpm: bid.cpm,
+ ttr: bid.ttr,
+ IsBid: bid.isBid
+ });
+ }
+ });
+ });
+
+ return responses;
+}
+
+function getWins() {
+ var wins = [];
+
+ Object.keys(cache.auctions).forEach(auctionId => {
+ Object.keys(cache.auctions[auctionId].bids).forEach(bidId => {
+ let auction = cache.auctions[auctionId];
+ let bid = auction.bids[bidId];
+ if (!(bid.sendStatus & WINSENT) && bid.won) {
+ bid.sendStatus |= WINSENT;
+
+ wins.push({
+ timeStamp: auction.timeStamp,
+ adUnit: bid.adUnit,
+ bidder: bid.bidder,
+ width: bid.width,
+ height: bid.height,
+ cpm: bid.cpm,
+ });
+ }
+ });
+ });
+
+ return wins;
+}
+
+function getTimeouts() {
+ var timeouts = [];
+
+ Object.keys(cache.auctions).forEach(auctionId => {
+ Object.keys(cache.auctions[auctionId].bids).forEach(bidId => {
+ let auction = cache.auctions[auctionId];
+ let bid = auction.bids[bidId];
+ if (!(bid.sendStatus & TIMEOUTSENT) && bid.timeout) {
+ bid.sendStatus |= TIMEOUTSENT;
+
+ timeouts.push({
+ bidder: bid.bidder,
+ adUnit: bid.adUnit,
+ timeStamp: auction.timeStamp
+ });
+ }
+ });
+ });
+
+ return timeouts;
+}
+
+adaptermanager.registerAnalyticsAdapter({
+ adapter: livewrappedAnalyticsAdapter,
+ code: 'livewrapped'
+});
+
+export default livewrappedAnalyticsAdapter;
diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js
new file mode 100644
index 00000000000..ebfe4aad2d4
--- /dev/null
+++ b/modules/livewrappedBidAdapter.js
@@ -0,0 +1,187 @@
+import * as utils from 'src/utils';
+import { registerBidder } from 'src/adapters/bidderFactory';
+import { config } from 'src/config';
+import find from 'core-js/library/fn/array/find';
+
+const BIDDER_CODE = 'livewrapped';
+export const URL = 'https://lwadm.com/ad';
+const VERSION = '1.1';
+
+export const spec = {
+ code: BIDDER_CODE,
+
+ /**
+ * Determines whether or not the given bid request is valid.
+ *
+ * Parameters should be
+ *
+ * adUnitId: LiveWrapped's id of the ad unit. Optional. A guid identifying the ad unit.
+ * adUnitName: LiveWrapped's name of the ad unit Optional. (Prebid's ad unit code will be used otherwise.)
+ * publisherId: Publisher id. Required if adUnitName is used or both adUnitName and adUnitId is omitted, otherwise optional.
+ * userId: A persistent user id if available. Optional.
+ * url: Page url Optional. Use if page url cannot be determined due to use of iframes.
+ * bidUrl: Bidding endpoint Optional.
+ * seats: List of bidders and seats Optional. {"bidder name": ["seat 1", "seat 2"], ...}
+ * deviceId: Device id if available Optional.
+ * ifa: Advertising ID Optional.
+ *
+ * @param {BidRequest} bid The bid params to validate.
+ * @return boolean True if this is a valid bid, and false otherwise.
+ */
+ isBidRequestValid: function(bid) {
+ return (bid.params.adUnitId || ((bid.params.adUnitName || bid.adUnitCode || bid.placementCode) && bid.params.publisherId)) !== undefined;
+ },
+
+ /**
+ * Make a server request from the list of BidRequests.
+ *
+ * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server.
+ * @return ServerRequest Info describing the request to the server.
+ */
+ buildRequests: function(bidRequests, bidderRequest) {
+ const userId = find(bidRequests, hasUserId);
+ const publisherId = find(bidRequests, hasPublisherId);
+ const auctionId = find(bidRequests, hasAuctionId);
+ let bidUrl = find(bidRequests, hasBidUrl);
+ let url = find(bidRequests, hasUrl);
+ let test = find(bidRequests, hasTestParam);
+ let seats = find(bidRequests, hasSeatsParam);
+ let deviceId = find(bidRequests, hasDeviceIdParam);
+ let ifa = find(bidRequests, hasIfaParam);
+ let tid = find(bidRequests, hasTidParam);
+ bidUrl = bidUrl ? bidUrl.params.bidUrl : URL;
+ url = url ? url.params.url : (config.getConfig('pageUrl') || utils.getTopWindowUrl());
+ test = test ? test.params.test : undefined;
+ var adRequests = bidRequests.map(bidToAdRequest);
+
+ const payload = {
+ auctionId: auctionId ? auctionId.auctionId : undefined,
+ publisherId: publisherId ? publisherId.params.publisherId : undefined,
+ userId: userId ? userId.params.userId : undefined,
+ url: url,
+ test: test,
+ seats: seats ? seats.params.seats : undefined,
+ deviceId: deviceId ? deviceId.params.deviceId : undefined,
+ ifa: ifa ? ifa.params.ifa : undefined,
+ tid: tid ? tid.params.tid : undefined,
+ version: VERSION,
+ gdprApplies: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.gdprApplies : false,
+ gdprConsent: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.consentString : undefined,
+ cookieSupport: !utils.isSafariBrowser() && utils.cookiesAreEnabled(),
+ adRequests: [...adRequests]
+ };
+ const payloadString = JSON.stringify(payload);
+ return {
+ method: 'POST',
+ url: bidUrl,
+ data: payloadString,
+ };
+ },
+
+ /**
+ * Unpack the response from the server into a list of bids.
+ *
+ * @param {*} serverResponse A successful response from the server.
+ * @return {Bid[]} An array of bids which were nested inside the server.
+ */
+ interpretResponse: function(serverResponse) {
+ const bidResponses = [];
+
+ serverResponse.body.ads.forEach(function(ad) {
+ let bidResponse = {
+ requestId: ad.bidId,
+ bidderCode: BIDDER_CODE,
+ cpm: ad.cpmBid,
+ width: ad.width,
+ height: ad.height,
+ ad: ad.tag,
+ ttl: ad.ttl,
+ creativeId: ad.creativeId,
+ netRevenue: true,
+ currency: serverResponse.body.currency
+ };
+
+ bidResponses.push(bidResponse);
+ });
+
+ return bidResponses;
+ },
+
+ getUserSyncs: function(syncOptions, serverResponses) {
+ if (serverResponses.length == 0) return [];
+
+ let syncList = [];
+ let userSync = serverResponses[0].body.pixels || [];
+
+ userSync.forEach(function(sync) {
+ if (syncOptions.pixelEnabled && sync.type == 'Redirect') {
+ syncList.push({type: 'image', url: sync.url});
+ }
+
+ if (syncOptions.iframeEnabled && sync.type == 'Iframe') {
+ syncList.push({type: 'iframe', url: sync.url});
+ }
+ });
+
+ return syncList;
+ }
+}
+
+function hasUserId(bid) {
+ return !!bid.params.userId;
+}
+
+function hasPublisherId(bid) {
+ return !!bid.params.publisherId;
+}
+
+function hasUrl(bid) {
+ return !!bid.params.url;
+}
+
+function hasBidUrl(bid) {
+ return !!bid.params.bidUrl;
+}
+
+function hasAuctionId(bid) {
+ return !!bid.auctionId;
+}
+
+function hasTestParam(bid) {
+ return !!bid.params.test;
+}
+
+function hasSeatsParam(bid) {
+ return !!bid.params.seats;
+}
+
+function hasDeviceIdParam(bid) {
+ return !!bid.params.deviceId;
+}
+
+function hasIfaParam(bid) {
+ return !!bid.params.ifa;
+}
+
+function hasTidParam(bid) {
+ return !!bid.params.tid;
+}
+
+function bidToAdRequest(bid) {
+ return {
+ adUnitId: bid.params.adUnitId,
+ callerAdUnitId: bid.params.adUnitName || bid.adUnitCode || bid.placementCode,
+ bidId: bid.bidId,
+ transactionId: bid.transactionId,
+ formats: bid.sizes.map(sizeToFormat)
+ };
+}
+
+function sizeToFormat(size) {
+ return {
+ width: size[0],
+ height: size[1]
+ }
+}
+
+registerBidder(spec);
diff --git a/modules/livewrappedBidAdapter.md b/modules/livewrappedBidAdapter.md
new file mode 100644
index 00000000000..15e3e8d533a
--- /dev/null
+++ b/modules/livewrappedBidAdapter.md
@@ -0,0 +1,28 @@
+# Overview
+
+**Module Name**: Livewrapped Bid Adapter
+**Module Type**: Bidder Adapter
+**Maintainer**: info@livewrapped.com
+
+# Description
+
+Connects to Livewrapped Header Bidding wrapper for bids.
+
+Livewrapped supports banner.
+
+# Test Parameters
+
+```
+var adUnits = [
+ {
+ code: 'banner-div',
+ sizes: [[300, 250], [300,600]],
+ bids: [{
+ bidder: 'livewrapped',
+ params: {
+ adUnitId: '6A32352E-BC17-4B94-B2A7-5BF1724417D7'
+ }
+ }]
+ }
+];
+```
diff --git a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js
new file mode 100644
index 00000000000..f7de9cd8101
--- /dev/null
+++ b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js
@@ -0,0 +1,265 @@
+import livewrappedAnalyticsAdapter, { BID_WON_TIMEOUT } from 'modules/livewrappedAnalyticsAdapter';
+import CONSTANTS from 'src/constants.json';
+import { config } from 'src/config';
+
+let events = require('src/events');
+let adaptermanager = require('src/adaptermanager');
+
+const {
+ EVENTS: {
+ AUCTION_INIT,
+ AUCTION_END,
+ BID_REQUESTED,
+ BID_RESPONSE,
+ BIDDER_DONE,
+ BID_WON,
+ BID_TIMEOUT,
+ SET_TARGETING
+ },
+ STATUS: {
+ GOOD
+ }
+} = CONSTANTS;
+
+const BID1 = {
+ width: 980,
+ height: 240,
+ cpm: 1.1,
+ timeToRespond: 200,
+ bidId: '2ecff0db240757',
+ adId: '2ecff0db240757',
+ auctionId: '25c6d7f5-699a-4bfc-87c9-996f915341fa',
+ getStatusCode() {
+ return CONSTANTS.STATUS.GOOD;
+ }
+};
+
+const BID2 = Object.assign({}, BID1, {
+ width: 300,
+ height: 250,
+ cpm: 2.2,
+ timeToRespond: 300,
+ bidId: '3ecff0db240757',
+ adId: '3ecff0db240757',
+});
+
+const MOCK = {
+ AUCTION_INIT: {
+ 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa',
+ },
+ BID_REQUESTED: {
+ 'bidder': 'livewrapped',
+ 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa',
+ 'bidderRequestId': '1be65d7958826a',
+ 'bids': [
+ {
+ 'bidder': 'livewrapped',
+ 'adUnitCode': 'panorama_d_1',
+ 'bidId': '2ecff0db240757',
+ },
+ {
+ 'bidder': 'livewrapped',
+ 'adUnitCode': 'box_d_1',
+ 'bidId': '3ecff0db240757',
+ }
+ ],
+ 'start': 1519149562216
+ },
+ BID_RESPONSE: [
+ BID1,
+ BID2
+ ],
+ AUCTION_END: {
+ },
+ BID_WON: [
+ Object.assign({}, BID1, {
+ 'status': 'rendered'
+ }),
+ Object.assign({}, BID2, {
+ 'status': 'rendered'
+ })
+ ],
+ BIDDER_DONE: {
+ 'bidderCode': 'livewrapped',
+ 'bids': [
+ BID1,
+ BID2
+ ]
+ },
+ BID_TIMEOUT: [
+ {
+ 'bidId': '2ecff0db240757',
+ 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa'
+ }
+ ]
+};
+
+const ANALYTICS_MESSAGE = {
+ publisherId: 'CC411485-42BC-4F92-8389-42C503EE38D7',
+ requests: [
+ {
+ adUnit: 'panorama_d_1',
+ bidder: 'livewrapped',
+ timeStamp: 1519149562216
+ },
+ {
+ adUnit: 'box_d_1',
+ bidder: 'livewrapped',
+ timeStamp: 1519149562216
+ }
+ ],
+ responses: [
+ {
+ timeStamp: 1519149562216,
+ adUnit: 'panorama_d_1',
+ bidder: 'livewrapped',
+ width: 980,
+ height: 240,
+ cpm: 1.1,
+ ttr: 200,
+ IsBid: true
+ },
+ {
+ timeStamp: 1519149562216,
+ adUnit: 'box_d_1',
+ bidder: 'livewrapped',
+ width: 300,
+ height: 250,
+ cpm: 2.2,
+ ttr: 300,
+ IsBid: true
+ }
+ ],
+ timeouts: [],
+ wins: [
+ {
+ timeStamp: 1519149562216,
+ adUnit: 'panorama_d_1',
+ bidder: 'livewrapped',
+ width: 980,
+ height: 240,
+ cpm: 1.1
+ },
+ {
+ timeStamp: 1519149562216,
+ adUnit: 'box_d_1',
+ bidder: 'livewrapped',
+ width: 300,
+ height: 250,
+ cpm: 2.2
+ }
+ ]
+};
+
+function performStandardAuction() {
+ events.emit(AUCTION_INIT, MOCK.AUCTION_INIT);
+ events.emit(BID_REQUESTED, MOCK.BID_REQUESTED);
+ events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]);
+ events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]);
+ events.emit(BIDDER_DONE, MOCK.BIDDER_DONE);
+ events.emit(AUCTION_END, MOCK.AUCTION_END);
+ events.emit(SET_TARGETING, MOCK.SET_TARGETING);
+ events.emit(BID_WON, MOCK.BID_WON[0]);
+ events.emit(BID_WON, MOCK.BID_WON[1]);
+}
+
+describe('Livewrapped analytics adapter', function () {
+ let sandbox;
+ let xhr;
+ let requests;
+ let clock;
+
+ beforeEach(function () {
+ sandbox = sinon.sandbox.create();
+
+ xhr = sandbox.useFakeXMLHttpRequest();
+ requests = [];
+ xhr.onCreate = request => requests.push(request);
+
+ sandbox.stub(events, 'getEvents').returns([]);
+
+ clock = sandbox.useFakeTimers(1519767013781);
+ });
+
+ afterEach(function () {
+ sandbox.restore();
+ config.resetConfig();
+ });
+
+ describe('when handling events', function () {
+ adaptermanager.registerAnalyticsAdapter({
+ code: 'livewrapped',
+ adapter: livewrappedAnalyticsAdapter
+ });
+
+ beforeEach(function () {
+ adaptermanager.enableAnalytics({
+ provider: 'livewrapped',
+ options: {
+ publisherId: 'CC411485-42BC-4F92-8389-42C503EE38D7'
+ }
+ });
+ });
+
+ afterEach(function () {
+ livewrappedAnalyticsAdapter.disableAnalytics();
+ });
+
+ it('should build a batched message from prebid events', function () {
+ performStandardAuction();
+
+ clock.tick(BID_WON_TIMEOUT + 1000);
+
+ expect(requests.length).to.equal(1);
+ let request = requests[0];
+
+ expect(request.url).to.equal('//lwadm.com/analytics/10');
+
+ let message = JSON.parse(request.requestBody);
+
+ expect(message).to.deep.equal(ANALYTICS_MESSAGE);
+ });
+
+ it('should send batched message without BID_WON if necessary and further BID_WON events individually', function () {
+ events.emit(AUCTION_INIT, MOCK.AUCTION_INIT);
+ events.emit(BID_REQUESTED, MOCK.BID_REQUESTED);
+ events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]);
+ events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]);
+ events.emit(BIDDER_DONE, MOCK.BIDDER_DONE);
+ events.emit(AUCTION_END, MOCK.AUCTION_END);
+ events.emit(SET_TARGETING, MOCK.SET_TARGETING);
+ events.emit(BID_WON, MOCK.BID_WON[0]);
+
+ clock.tick(BID_WON_TIMEOUT + 1000);
+
+ events.emit(BID_WON, MOCK.BID_WON[1]);
+
+ expect(requests.length).to.equal(2);
+
+ let message = JSON.parse(requests[0].requestBody);
+ expect(message.wins.length).to.equal(1);
+ expect(message.requests).to.deep.equal(ANALYTICS_MESSAGE.requests);
+ expect(message.wins[0]).to.deep.equal(ANALYTICS_MESSAGE.wins[0]);
+
+ message = JSON.parse(requests[1].requestBody);
+ expect(message.wins.length).to.equal(1);
+ expect(message.wins[0]).to.deep.equal(ANALYTICS_MESSAGE.wins[1]);
+ });
+
+ it('should properly mark bids as timed out', function () {
+ events.emit(AUCTION_INIT, MOCK.AUCTION_INIT);
+ events.emit(BID_REQUESTED, MOCK.BID_REQUESTED);
+ events.emit(BID_TIMEOUT, MOCK.BID_TIMEOUT);
+ events.emit(AUCTION_END, MOCK.AUCTION_END);
+
+ clock.tick(BID_WON_TIMEOUT + 1000);
+
+ expect(requests.length).to.equal(1);
+
+ let message = JSON.parse(requests[0].requestBody);
+ expect(message.timeouts.length).to.equal(1);
+ expect(message.timeouts[0].bidder).to.equal('livewrapped');
+ expect(message.timeouts[0].adUnit).to.equal('panorama_d_1');
+ });
+ });
+});
diff --git a/test/spec/modules/livewrappedBidAdapter_spec.js b/test/spec/modules/livewrappedBidAdapter_spec.js
new file mode 100644
index 00000000000..64a70b946ef
--- /dev/null
+++ b/test/spec/modules/livewrappedBidAdapter_spec.js
@@ -0,0 +1,609 @@
+import {expect} from 'chai';
+import {spec} from 'modules/livewrappedBidAdapter';
+import {config} from 'src/config';
+import * as utils from 'src/utils';
+
+describe('Livewrapped adapter tests', function () {
+ let sandbox,
+ bidderRequest;
+
+ beforeEach(function () {
+ sandbox = sinon.sandbox.create();
+
+ bidderRequest = {
+ bidderCode: 'livewrapped',
+ auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a',
+ bidderRequestId: '178e34bad3658f',
+ bids: [
+ {
+ bidder: 'livewrapped',
+ params: {
+ adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37',
+ publisherId: '26947112-2289-405D-88C1-A7340C57E63E',
+ userId: 'user id',
+ url: 'http://www.domain.com',
+ seats: {'dsp': ['seat 1']}
+ },
+ adUnitCode: 'panorama_d_1',
+ sizes: [[980, 240], [980, 120]],
+ bidId: '2ffb201a808da7',
+ bidderRequestId: '178e34bad3658f',
+ auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C',
+ transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D'
+ }
+ ],
+ start: 1472239426002,
+ auctionStart: 1472239426000,
+ timeout: 5000
+ };
+ });
+
+ afterEach(function () {
+ sandbox.restore();
+ });
+
+ describe('isBidRequestValid', function() {
+ it('should accept a request with id only as valid', function() {
+ let bid = {params: {adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37'}};
+
+ let result = spec.isBidRequestValid(bid);
+
+ expect(result).to.be.true;
+ });
+
+ it('should accept a request with adUnitName and PublisherId as valid', function() {
+ let bid = {params: {adUnitName: 'panorama_d_1', publisherId: '26947112-2289-405D-88C1-A7340C57E63E'}};
+
+ let result = spec.isBidRequestValid(bid);
+
+ expect(result).to.be.true;
+ });
+
+ it('should accept a request with adUnitCode and PublisherId as valid', function() {
+ let bid = {adUnitCode: 'panorama_d_1', params: {publisherId: '26947112-2289-405D-88C1-A7340C57E63E'}};
+
+ let result = spec.isBidRequestValid(bid);
+
+ expect(result).to.be.true;
+ });
+
+ it('should accept a request with placementCode and PublisherId as valid', function() {
+ let bid = {placementCode: 'panorama_d_1', params: {publisherId: '26947112-2289-405D-88C1-A7340C57E63E'}};
+
+ let result = spec.isBidRequestValid(bid);
+
+ expect(result).to.be.true;
+ });
+
+ it('should not accept a request with adUnitName, adUnitCode, placementCode but no PublisherId as valid', function() {
+ let bid = {placementCode: 'panorama_d_1', adUnitCode: 'panorama_d_1', params: {adUnitName: 'panorama_d_1'}};
+
+ let result = spec.isBidRequestValid(bid);
+
+ expect(result).to.be.false;
+ });
+ });
+
+ describe('buildRequests', function() {
+ it('should make a well-formed single request object', function() {
+ sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false);
+ sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true);
+ let result = spec.buildRequests(bidderRequest.bids, bidderRequest);
+ let data = JSON.parse(result.data);
+
+ expect(result.url).to.equal('https://lwadm.com/ad');
+
+ let expectedQuery = {
+ auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C',
+ publisherId: '26947112-2289-405D-88C1-A7340C57E63E',
+ userId: 'user id',
+ url: 'http://www.domain.com',
+ seats: {'dsp': ['seat 1']},
+ version: '1.1',
+ cookieSupport: true,
+ gdprApplies: false,
+ adRequests: [{
+ adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37',
+ callerAdUnitId: 'panorama_d_1',
+ bidId: '2ffb201a808da7',
+ transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D',
+ formats: [{width: 980, height: 240}, {width: 980, height: 120}]
+ }]
+ };
+
+ expect(data).to.deep.equal(expectedQuery);
+ });
+
+ it('should make a well-formed multiple request object', function() {
+ sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false);
+ sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true);
+ let multiplebidRequest = clone(bidderRequest);
+ multiplebidRequest.bids.push(clone(bidderRequest.bids[0]));
+ multiplebidRequest.bids[1].adUnitCode = 'box_d_1';
+ multiplebidRequest.bids[1].sizes = [[300, 250]];
+ multiplebidRequest.bids[1].bidId = '3ffb201a808da7';
+ delete multiplebidRequest.bids[1].params.adUnitId;
+
+ let result = spec.buildRequests(multiplebidRequest.bids, multiplebidRequest);
+ let data = JSON.parse(result.data);
+
+ expect(result.url).to.equal('https://lwadm.com/ad');
+
+ let expectedQuery = {
+ auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C',
+ publisherId: '26947112-2289-405D-88C1-A7340C57E63E',
+ userId: 'user id',
+ url: 'http://www.domain.com',
+ seats: {'dsp': ['seat 1']},
+ version: '1.1',
+ cookieSupport: true,
+ gdprApplies: false,
+ adRequests: [{
+ adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37',
+ callerAdUnitId: 'panorama_d_1',
+ bidId: '2ffb201a808da7',
+ transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D',
+ formats: [{width: 980, height: 240}, {width: 980, height: 120}]
+ }, {
+ callerAdUnitId: 'box_d_1',
+ bidId: '3ffb201a808da7',
+ transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D',
+ formats: [{width: 300, height: 250}]
+ }]
+ };
+
+ expect(data).to.deep.equal(expectedQuery);
+ });
+
+ it('should make a well-formed single request object with AdUnitName', function() {
+ sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false);
+ sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true);
+ let testbidRequest = clone(bidderRequest);
+ testbidRequest.bids[0].params.adUnitName = 'caller id 1';
+ delete testbidRequest.bids[0].params.adUnitId;
+ let result = spec.buildRequests(testbidRequest.bids, testbidRequest);
+ let data = JSON.parse(result.data);
+
+ expect(result.url).to.equal('https://lwadm.com/ad');
+
+ let expectedQuery = {
+ auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C',
+ publisherId: '26947112-2289-405D-88C1-A7340C57E63E',
+ userId: 'user id',
+ url: 'http://www.domain.com',
+ seats: {'dsp': ['seat 1']},
+ version: '1.1',
+ cookieSupport: true,
+ gdprApplies: false,
+ adRequests: [{
+ callerAdUnitId: 'caller id 1',
+ bidId: '2ffb201a808da7',
+ transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D',
+ formats: [{width: 980, height: 240}, {width: 980, height: 120}]
+ }]
+ };
+
+ expect(data).to.deep.equal(expectedQuery);
+ });
+
+ it('should make a well-formed single request object with less parameters', function() {
+ sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false);
+ sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true);
+ let testbidRequest = clone(bidderRequest);
+ delete testbidRequest.bids[0].params.userId;
+ delete testbidRequest.bids[0].params.seats;
+ delete testbidRequest.bids[0].params.adUnitId;
+ let result = spec.buildRequests(testbidRequest.bids, testbidRequest);
+ let data = JSON.parse(result.data);
+
+ expect(result.url).to.equal('https://lwadm.com/ad');
+
+ let expectedQuery = {
+ auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C',
+ publisherId: '26947112-2289-405D-88C1-A7340C57E63E',
+ url: 'http://www.domain.com',
+ version: '1.1',
+ cookieSupport: true,
+ gdprApplies: false,
+ adRequests: [{
+ callerAdUnitId: 'panorama_d_1',
+ bidId: '2ffb201a808da7',
+ transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D',
+ formats: [{width: 980, height: 240}, {width: 980, height: 120}]
+ }]
+ };
+
+ expect(data).to.deep.equal(expectedQuery);
+ });
+
+ it('should make a well-formed single request object with less parameters, no publisherId', function() {
+ sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false);
+ sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true);
+ let testbidRequest = clone(bidderRequest);
+ delete testbidRequest.bids[0].params.userId;
+ delete testbidRequest.bids[0].params.seats;
+ delete testbidRequest.bids[0].params.publisherId;
+ let result = spec.buildRequests(testbidRequest.bids, testbidRequest);
+ let data = JSON.parse(result.data);
+
+ expect(result.url).to.equal('https://lwadm.com/ad');
+
+ let expectedQuery = {
+ auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C',
+ url: 'http://www.domain.com',
+ version: '1.1',
+ cookieSupport: true,
+ gdprApplies: false,
+ adRequests: [{
+ adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37',
+ callerAdUnitId: 'panorama_d_1',
+ bidId: '2ffb201a808da7',
+ transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D',
+ formats: [{width: 980, height: 240}, {width: 980, height: 120}]
+ }]
+ };
+
+ expect(data).to.deep.equal(expectedQuery);
+ });
+
+ it('should make a well-formed single request object with app parameters', function() {
+ sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false);
+ sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true);
+ let testbidRequest = clone(bidderRequest);
+ delete testbidRequest.bids[0].params.userId;
+ delete testbidRequest.bids[0].params.seats;
+ delete testbidRequest.bids[0].params.adUnitId;
+ testbidRequest.bids[0].params.deviceId = 'deviceid';
+ testbidRequest.bids[0].params.ifa = 'ifa';
+ let result = spec.buildRequests(testbidRequest.bids, testbidRequest);
+ let data = JSON.parse(result.data);
+
+ let expectedQuery = {
+ auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C',
+ publisherId: '26947112-2289-405D-88C1-A7340C57E63E',
+ url: 'http://www.domain.com',
+ version: '1.1',
+ deviceId: 'deviceid',
+ ifa: 'ifa',
+ cookieSupport: true,
+ gdprApplies: false,
+ adRequests: [{
+ callerAdUnitId: 'panorama_d_1',
+ bidId: '2ffb201a808da7',
+ transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D',
+ formats: [{width: 980, height: 240}, {width: 980, height: 120}]
+ }]
+ };
+
+ expect(data).to.deep.equal(expectedQuery);
+ });
+
+ it('should make a well-formed single request object with debug parameters', function() {
+ sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false);
+ sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true);
+ let testbidRequest = clone(bidderRequest);
+ delete testbidRequest.bids[0].params.userId;
+ delete testbidRequest.bids[0].params.seats;
+ delete testbidRequest.bids[0].params.adUnitId;
+ testbidRequest.bids[0].params.tid = 'tracking id';
+ testbidRequest.bids[0].params.test = true;
+ let result = spec.buildRequests(testbidRequest.bids, testbidRequest);
+ let data = JSON.parse(result.data);
+
+ let expectedQuery = {
+ auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C',
+ publisherId: '26947112-2289-405D-88C1-A7340C57E63E',
+ url: 'http://www.domain.com',
+ version: '1.1',
+ tid: 'tracking id',
+ test: true,
+ cookieSupport: true,
+ gdprApplies: false,
+ adRequests: [{
+ callerAdUnitId: 'panorama_d_1',
+ bidId: '2ffb201a808da7',
+ transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D',
+ formats: [{width: 980, height: 240}, {width: 980, height: 120}]
+ }]
+ };
+
+ expect(data).to.deep.equal(expectedQuery);
+ });
+
+ it('should pass gdpr parameters', function() {
+ sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false);
+ sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true);
+ let testRequest = clone(bidderRequest);
+ testRequest.gdprConsent = {
+ gdprApplies: true,
+ consentString: 'test'
+ };
+ let result = spec.buildRequests(testRequest.bids, testRequest);
+ let data = JSON.parse(result.data);
+
+ expect(result.url).to.equal('https://lwadm.com/ad');
+
+ let expectedQuery = {
+ auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C',
+ publisherId: '26947112-2289-405D-88C1-A7340C57E63E',
+ userId: 'user id',
+ url: 'http://www.domain.com',
+ seats: {'dsp': ['seat 1']},
+ version: '1.1',
+ cookieSupport: true,
+ gdprApplies: true,
+ gdprConsent: 'test',
+ adRequests: [{
+ adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37',
+ callerAdUnitId: 'panorama_d_1',
+ bidId: '2ffb201a808da7',
+ transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D',
+ formats: [{width: 980, height: 240}, {width: 980, height: 120}]
+ }]
+ };
+
+ expect(data).to.deep.equal(expectedQuery);
+ });
+
+ it('should pass no cookie support', function() {
+ sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => false);
+ sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false);
+ let result = spec.buildRequests(bidderRequest.bids, bidderRequest);
+ let data = JSON.parse(result.data);
+
+ expect(result.url).to.equal('https://lwadm.com/ad');
+
+ let expectedQuery = {
+ auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C',
+ publisherId: '26947112-2289-405D-88C1-A7340C57E63E',
+ userId: 'user id',
+ url: 'http://www.domain.com',
+ seats: {'dsp': ['seat 1']},
+ version: '1.1',
+ gdprApplies: false,
+ cookieSupport: false,
+ adRequests: [{
+ adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37',
+ callerAdUnitId: 'panorama_d_1',
+ bidId: '2ffb201a808da7',
+ transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D',
+ formats: [{width: 980, height: 240}, {width: 980, height: 120}]
+ }]
+ };
+
+ expect(data).to.deep.equal(expectedQuery);
+ });
+
+ it('should pass no cookie support Safari', function() {
+ sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true);
+ sandbox.stub(utils, 'isSafariBrowser').callsFake(() => true);
+ let result = spec.buildRequests(bidderRequest.bids, bidderRequest);
+ let data = JSON.parse(result.data);
+
+ expect(result.url).to.equal('https://lwadm.com/ad');
+
+ let expectedQuery = {
+ auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C',
+ publisherId: '26947112-2289-405D-88C1-A7340C57E63E',
+ userId: 'user id',
+ url: 'http://www.domain.com',
+ seats: {'dsp': ['seat 1']},
+ version: '1.1',
+ gdprApplies: false,
+ cookieSupport: false,
+ adRequests: [{
+ adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37',
+ callerAdUnitId: 'panorama_d_1',
+ bidId: '2ffb201a808da7',
+ transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D',
+ formats: [{width: 980, height: 240}, {width: 980, height: 120}]
+ }]
+ };
+
+ expect(data).to.deep.equal(expectedQuery);
+ });
+
+ it('should use params.url, then config pageUrl, then getTopWindowUrl', function() {
+ let testRequest = clone(bidderRequest);
+ sandbox.stub(utils, 'getTopWindowUrl').callsFake(() => 'http://www.topurl.com');
+
+ let result = spec.buildRequests(testRequest.bids, testRequest);
+ let data = JSON.parse(result.data);
+
+ expect(data.url).to.equal('http://www.domain.com');
+
+ delete testRequest.bids[0].params.url;
+
+ result = spec.buildRequests(testRequest.bids, testRequest);
+ data = JSON.parse(result.data);
+
+ expect(data.url).to.equal('http://www.topurl.com');
+
+ let origGetConfig = config.getConfig;
+ sandbox.stub(config, 'getConfig').callsFake(function (key) {
+ if (key === 'pageUrl') {
+ return 'http://www.configurl.com';
+ }
+ return origGetConfig.apply(config, arguments);
+ });
+
+ result = spec.buildRequests(testRequest.bids, testRequest);
+ data = JSON.parse(result.data);
+
+ expect(data.url).to.equal('http://www.configurl.com');
+ });
+ });
+
+ describe('interpretResponse', function () {
+ it('should handle single success response', function() {
+ let lwResponse = {
+ ads: [
+ {
+ id: '28e5ddf4-3c01-11e8-86a7-0a44794250d4',
+ callerId: 'site_outsider_0',
+ tag: 'ad',
+ width: 300,
+ height: 250,
+ cpmBid: 2.565917,
+ bidId: '32e50fad901ae89',
+ auctionId: '13e674db-d4d8-4e19-9d28-ff38177db8bf',
+ creativeId: '52cbd598-2715-4c43-a06f-229fc170f945:427077',
+ ttl: 120
+ }
+ ],
+ currency: 'USD'
+ };
+
+ let expectedResponse = [{
+ requestId: '32e50fad901ae89',
+ bidderCode: 'livewrapped',
+ cpm: 2.565917,
+ width: 300,
+ height: 250,
+ ad: 'ad',
+ ttl: 120,
+ creativeId: '52cbd598-2715-4c43-a06f-229fc170f945:427077',
+ netRevenue: true,
+ currency: 'USD'
+ }];
+
+ let bids = spec.interpretResponse({body: lwResponse});
+
+ expect(bids).to.deep.equal(expectedResponse);
+ })
+
+ it('should handle multiple success response', function() {
+ let lwResponse = {
+ ads: [
+ {
+ id: '28e5ddf4-3c01-11e8-86a7-0a44794250d4',
+ callerId: 'site_outsider_0',
+ tag: 'ad1',
+ width: 300,
+ height: 250,
+ cpmBid: 2.565917,
+ bidId: '32e50fad901ae89',
+ auctionId: '13e674db-d4d8-4e19-9d28-ff38177db8bf',
+ creativeId: '52cbd598-2715-4c43-a06f-229fc170f945:427077',
+ ttl: 120
+ },
+ {
+ id: '38e5ddf4-3c01-11e8-86a7-0a44794250d4',
+ callerId: 'site_outsider_1',
+ tag: 'ad2',
+ width: 980,
+ height: 240,
+ cpmBid: 3.565917,
+ bidId: '42e50fad901ae89',
+ auctionId: '13e674db-d4d8-4e19-9d28-ff38177db8bf',
+ creativeId: '62cbd598-2715-4c43-a06f-229fc170f945:427077',
+ ttl: 120
+ }
+ ],
+ currency: 'USD'
+ };
+
+ let expectedResponse = [{
+ requestId: '32e50fad901ae89',
+ bidderCode: 'livewrapped',
+ cpm: 2.565917,
+ width: 300,
+ height: 250,
+ ad: 'ad1',
+ ttl: 120,
+ creativeId: '52cbd598-2715-4c43-a06f-229fc170f945:427077',
+ netRevenue: true,
+ currency: 'USD'
+ }, {
+ requestId: '42e50fad901ae89',
+ bidderCode: 'livewrapped',
+ cpm: 3.565917,
+ width: 980,
+ height: 240,
+ ad: 'ad2',
+ ttl: 120,
+ creativeId: '62cbd598-2715-4c43-a06f-229fc170f945:427077',
+ netRevenue: true,
+ currency: 'USD'
+ }];
+
+ let bids = spec.interpretResponse({body: lwResponse});
+
+ expect(bids).to.deep.equal(expectedResponse);
+ })
+ });
+
+ describe('user sync', function () {
+ let serverResponses;
+
+ beforeEach(function () {
+ serverResponses = [{
+ body: {
+ pixels: [
+ {type: 'Redirect', url: 'http://pixelsync'},
+ {type: 'Iframe', url: 'http://iframesync'}
+ ]
+ }
+ }];
+ });
+
+ it('should return empty if no server responses', function() {
+ let syncs = spec.getUserSyncs({
+ pixelEnabled: true,
+ iframeEnabled: true
+ }, []);
+
+ let expectedResponse = [];
+
+ expect(syncs).to.deep.equal(expectedResponse)
+ });
+
+ it('should return empty if no user sync', function() {
+ let syncs = spec.getUserSyncs({
+ pixelEnabled: true,
+ iframeEnabled: true
+ }, [{body: {}}]);
+
+ let expectedResponse = [];
+
+ expect(syncs).to.deep.equal(expectedResponse)
+ });
+
+ it('should returns pixel and iframe user sync', function() {
+ let syncs = spec.getUserSyncs({
+ pixelEnabled: true,
+ iframeEnabled: true
+ }, serverResponses);
+
+ let expectedResponse = [{type: 'image', url: 'http://pixelsync'}, {type: 'iframe', url: 'http://iframesync'}];
+
+ expect(syncs).to.deep.equal(expectedResponse)
+ });
+
+ it('should returns pixel only if iframe not supported user sync', function() {
+ let syncs = spec.getUserSyncs({
+ pixelEnabled: true,
+ iframeEnabled: false
+ }, serverResponses);
+
+ let expectedResponse = [{type: 'image', url: 'http://pixelsync'}];
+
+ expect(syncs).to.deep.equal(expectedResponse)
+ });
+
+ it('should returns iframe only if pixel not supported user sync', function() {
+ let syncs = spec.getUserSyncs({
+ pixelEnabled: false,
+ iframeEnabled: true
+ }, serverResponses);
+
+ let expectedResponse = [{type: 'iframe', url: 'http://iframesync'}];
+
+ expect(syncs).to.deep.equal(expectedResponse)
+ });
+ });
+});
+
+function clone(obj) {
+ return JSON.parse(JSON.stringify(obj));
+}