diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js
new file mode 100644
index 00000000000..1a9ccfdf824
--- /dev/null
+++ b/modules/minutemediaBidAdapter.js
@@ -0,0 +1,360 @@
+import { logWarn, isArray, isFn, deepAccess, isEmpty, contains, timestamp, getBidIdParameter } from '../src/utils.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {VIDEO} from '../src/mediaTypes.js';
+import {config} from '../src/config.js';
+
+const SUPPORTED_AD_TYPES = [VIDEO];
+const BIDDER_CODE = 'minutemedia';
+const ADAPTER_VERSION = '5.0.1';
+const TTL = 360;
+const CURRENCY = 'USD';
+const SELLER_ENDPOINT = 'https://hb.minutemedia-prebid.com/';
+const MODES = {
+ PRODUCTION: 'hb-mm',
+ TEST: 'hb-mm-test'
+}
+const SUPPORTED_SYNC_METHODS = {
+ IFRAME: 'iframe',
+ PIXEL: 'pixel'
+}
+
+export const spec = {
+ code: BIDDER_CODE,
+ gvlid: 918,
+ version: ADAPTER_VERSION,
+ supportedMediaTypes: SUPPORTED_AD_TYPES,
+ isBidRequestValid: function(bidRequest) {
+ if (!bidRequest.params) {
+ logWarn('no params have been set to MinuteMedia adapter');
+ return false;
+ }
+
+ if (!bidRequest.params.org) {
+ logWarn('org is a mandatory param for MinuteMedia adapter');
+ return false;
+ }
+
+ return true;
+ },
+ buildRequests: function (bidRequests, bidderRequest) {
+ if (bidRequests.length === 0) {
+ return [];
+ }
+
+ const requests = [];
+
+ bidRequests.forEach(bid => {
+ requests.push(buildVideoRequest(bid, bidderRequest));
+ });
+
+ return requests;
+ },
+ interpretResponse: function({body}) {
+ const bidResponses = [];
+
+ const bidResponse = {
+ requestId: body.requestId,
+ cpm: body.cpm,
+ width: body.width,
+ height: body.height,
+ creativeId: body.requestId,
+ currency: body.currency,
+ netRevenue: body.netRevenue,
+ ttl: body.ttl || TTL,
+ vastXml: body.vastXml,
+ mediaType: VIDEO
+ };
+
+ if (body.adomain && body.adomain.length) {
+ bidResponse.meta = {};
+ bidResponse.meta.advertiserDomains = body.adomain
+ }
+ bidResponses.push(bidResponse);
+
+ return bidResponses;
+ },
+ getUserSyncs: function(syncOptions, serverResponses) {
+ const syncs = [];
+ for (const response of serverResponses) {
+ if (syncOptions.iframeEnabled && response.body.userSyncURL) {
+ syncs.push({
+ type: 'iframe',
+ url: response.body.userSyncURL
+ });
+ }
+ if (syncOptions.pixelEnabled && isArray(response.body.userSyncPixels)) {
+ const pixels = response.body.userSyncPixels.map(pixel => {
+ return {
+ type: 'image',
+ url: pixel
+ }
+ })
+ syncs.push(...pixels)
+ }
+ }
+ return syncs;
+ }
+};
+
+registerBidder(spec);
+
+/**
+ * Get floor price
+ * @param bid {bid}
+ * @returns {Number}
+ */
+function getFloor(bid) {
+ if (!isFn(bid.getFloor)) {
+ return 0;
+ }
+ let floorResult = bid.getFloor({
+ currency: CURRENCY,
+ mediaType: VIDEO,
+ size: '*'
+ });
+ return floorResult.currency === CURRENCY && floorResult.floor ? floorResult.floor : 0;
+}
+
+/**
+ * Build the video request
+ * @param bid {bid}
+ * @param bidderRequest {bidderRequest}
+ * @returns {Object}
+ */
+function buildVideoRequest(bid, bidderRequest) {
+ const sellerParams = generateParameters(bid, bidderRequest);
+ const {params} = bid;
+ return {
+ method: 'GET',
+ url: getEndpoint(params.testMode),
+ data: sellerParams
+ };
+}
+
+/**
+ * Get the the ad size from the bid
+ * @param bid {bid}
+ * @returns {Array}
+ */
+function getSizes(bid) {
+ if (deepAccess(bid, 'mediaTypes.video.sizes')) {
+ return bid.mediaTypes.video.sizes[0];
+ } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) {
+ return bid.sizes[0];
+ }
+ return [];
+}
+
+/**
+ * Get schain string value
+ * @param schainObject {Object}
+ * @returns {string}
+ */
+function getSupplyChain(schainObject) {
+ if (isEmpty(schainObject)) {
+ return '';
+ }
+ let scStr = `${schainObject.ver},${schainObject.complete}`;
+ schainObject.nodes.forEach((node) => {
+ scStr += '!';
+ scStr += `${getEncodedValIfNotEmpty(node.asi)},`;
+ scStr += `${getEncodedValIfNotEmpty(node.sid)},`;
+ scStr += `${node.hp ? encodeURIComponent(node.hp) : ''},`;
+ scStr += `${getEncodedValIfNotEmpty(node.rid)},`;
+ scStr += `${getEncodedValIfNotEmpty(node.name)},`;
+ scStr += `${getEncodedValIfNotEmpty(node.domain)}`;
+ });
+ return scStr;
+}
+
+/**
+ * Get encoded node value
+ * @param val {string}
+ * @returns {string}
+ */
+function getEncodedValIfNotEmpty(val) {
+ return !isEmpty(val) ? encodeURIComponent(val) : '';
+}
+
+/**
+ * Get preferred user-sync method based on publisher configuration
+ * @param bidderCode {string}
+ * @returns {string}
+ */
+function getAllowedSyncMethod(filterSettings, bidderCode) {
+ const iframeConfigsToCheck = ['all', 'iframe'];
+ const pixelConfigToCheck = 'image';
+ if (filterSettings && iframeConfigsToCheck.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) {
+ return SUPPORTED_SYNC_METHODS.IFRAME;
+ }
+ if (!filterSettings || !filterSettings[pixelConfigToCheck] || isSyncMethodAllowed(filterSettings[pixelConfigToCheck], bidderCode)) {
+ return SUPPORTED_SYNC_METHODS.PIXEL;
+ }
+}
+
+/**
+ * Check if sync rule is supported
+ * @param syncRule {Object}
+ * @param bidderCode {string}
+ * @returns {boolean}
+ */
+function isSyncMethodAllowed(syncRule, bidderCode) {
+ if (!syncRule) {
+ return false;
+ }
+ const isInclude = syncRule.filter === 'include';
+ const bidders = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode];
+ return isInclude && contains(bidders, bidderCode);
+}
+
+/**
+ * Get the seller endpoint
+ * @param testMode {boolean}
+ * @returns {string}
+ */
+function getEndpoint(testMode) {
+ return testMode
+ ? SELLER_ENDPOINT + MODES.TEST
+ : SELLER_ENDPOINT + MODES.PRODUCTION;
+}
+
+/**
+ * get device type
+ * @param uad {ua}
+ * @returns {string}
+ */
+function getDeviceType(ua) {
+ if (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i
+ .test(ua.toLowerCase())) {
+ return '5';
+ }
+ if (/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i
+ .test(ua.toLowerCase())) {
+ return '4';
+ }
+ if (/smart[-_\s]?tv|hbbtv|appletv|googletv|hdmi|netcast|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b/i
+ .test(ua.toLowerCase())) {
+ return '3';
+ }
+ return '1';
+}
+
+/**
+ * Generate query parameters for the request
+ * @param bid {bid}
+ * @param bidderRequest {bidderRequest}
+ * @returns {Object}
+ */
+function generateParameters(bid, bidderRequest) {
+ const {params} = bid;
+ const timeout = config.getConfig('bidderTimeout');
+ const {syncEnabled, filterSettings} = config.getConfig('userSync') || {};
+ const [width, height] = getSizes(bid);
+ const {bidderCode} = bidderRequest;
+ const domain = window.location.hostname;
+
+ // fix floor price in case of NAN
+ if (isNaN(params.floorPrice)) {
+ params.floorPrice = 0;
+ }
+
+ const requestParams = {
+ wrapper_type: 'prebidjs',
+ wrapper_vendor: '$$PREBID_GLOBAL$$',
+ wrapper_version: '$prebid.version$',
+ adapter_version: ADAPTER_VERSION,
+ auction_start: timestamp(),
+ ad_unit_code: getBidIdParameter('adUnitCode', bid),
+ tmax: timeout,
+ width: width,
+ height: height,
+ publisher_id: params.org,
+ floor_price: Math.max(getFloor(bid), params.floorPrice),
+ ua: navigator.userAgent,
+ bid_id: getBidIdParameter('bidId', bid),
+ bidder_request_id: getBidIdParameter('bidderRequestId', bid),
+ transaction_id: getBidIdParameter('transactionId', bid),
+ session_id: getBidIdParameter('auctionId', bid),
+ publisher_name: domain,
+ site_domain: domain,
+ dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0,
+ device_type: getDeviceType(navigator.userAgent)
+ };
+
+ const userIdsParam = getBidIdParameter('userId', bid);
+ if (userIdsParam) {
+ requestParams.userIds = JSON.stringify(userIdsParam);
+ }
+
+ const ortb2Metadata = config.getConfig('ortb2') || {};
+ if (ortb2Metadata.site) {
+ requestParams.site_metadata = JSON.stringify(ortb2Metadata.site);
+ }
+ if (ortb2Metadata.user) {
+ requestParams.user_metadata = JSON.stringify(ortb2Metadata.user);
+ }
+
+ const playbackMethod = deepAccess(bid, 'mediaTypes.video.playbackmethod');
+ if (playbackMethod) {
+ requestParams.playback_method = playbackMethod;
+ }
+ const placement = deepAccess(bid, 'mediaTypes.video.placement');
+ if (placement) {
+ requestParams.placement = placement;
+ }
+ const pos = deepAccess(bid, 'mediaTypes.video.pos');
+ if (pos) {
+ requestParams.pos = pos;
+ }
+ const minduration = deepAccess(bid, 'mediaTypes.video.minduration');
+ if (minduration) {
+ requestParams.min_duration = minduration;
+ }
+ const maxduration = deepAccess(bid, 'mediaTypes.video.maxduration');
+ if (maxduration) {
+ requestParams.max_duration = maxduration;
+ }
+ const skip = deepAccess(bid, 'mediaTypes.video.skip');
+ if (skip) {
+ requestParams.skip = skip;
+ }
+ const linearity = deepAccess(bid, 'mediaTypes.video.linearity');
+ if (linearity) {
+ requestParams.linearity = linearity;
+ }
+
+ if (params.placementId) {
+ requestParams.placement_id = params.placementId;
+ }
+
+ if (syncEnabled) {
+ const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode);
+ if (allowedSyncMethod) {
+ requestParams.cs_method = allowedSyncMethod;
+ }
+ }
+
+ if (bidderRequest.uspConsent) {
+ requestParams.us_privacy = bidderRequest.uspConsent;
+ }
+
+ if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) {
+ requestParams.gdpr = bidderRequest.gdprConsent.gdprApplies;
+ requestParams.gdpr_consent = bidderRequest.gdprConsent.consentString;
+ }
+
+ if (params.ifa) {
+ requestParams.ifa = params.ifa;
+ }
+
+ if (bid.schain) {
+ requestParams.schain = getSupplyChain(bid.schain);
+ }
+
+ if (bidderRequest && bidderRequest.refererInfo) {
+ requestParams.referrer = deepAccess(bidderRequest, 'refererInfo.referer');
+ requestParams.page_url = config.getConfig('pageUrl') || deepAccess(window, 'location.href');
+ }
+
+ return requestParams;
+}
diff --git a/modules/minutemediaBidAdapter.md b/modules/minutemediaBidAdapter.md
new file mode 100644
index 00000000000..348cc586e08
--- /dev/null
+++ b/modules/minutemediaBidAdapter.md
@@ -0,0 +1,51 @@
+#Overview
+
+Module Name: MinuteMedia Bidder Adapter
+
+Module Type: Bidder Adapter
+
+Maintainer: hb@minutemedia.com
+
+
+# Description
+
+Module that connects to MinuteMedia's demand sources.
+
+The MinuteMedia adapter requires setup and approval from the MinuteMedia. Please reach out to hb@minutemedia.com to create an MinuteMedia account.
+
+The adapter supports Video(instream).
+
+# Bid Parameters
+## Video
+
+| Name | Scope | Type | Description | Example
+| ---- | ----- | ---- | ----------- | -------
+| `org` | required | String | MinuteMedia publisher Id provided by your MinuteMedia representative | "56f91cd4d3e3660002000033"
+| `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00
+| `placementId` | optional | String | A unique placement identifier | "12345678"
+| `testMode` | optional | Boolean | This activates the test mode | false
+
+# Test Parameters
+```javascript
+var adUnits = [
+ {
+ code: 'dfp-video-div',
+ sizes: [[640, 480]],
+ mediaTypes: {
+ video: {
+ playerSize: [[640, 480]],
+ context: 'instream'
+ }
+ },
+ bids: [{
+ bidder: 'minutemedia',
+ params: {
+ org: '56f91cd4d3e3660002000033', // Required
+ floorPrice: 2.00, // Optional
+ placementId: '12345678', // Optional
+ testMode: false // Optional
+ }
+ }]
+ }
+ ];
+```
diff --git a/test/spec/modules/minutemediaBidAdapter_spec.js b/test/spec/modules/minutemediaBidAdapter_spec.js
new file mode 100644
index 00000000000..b1ad7f96bc4
--- /dev/null
+++ b/test/spec/modules/minutemediaBidAdapter_spec.js
@@ -0,0 +1,405 @@
+import { expect } from 'chai';
+import { spec } from 'modules/minutemediaBidAdapter.js';
+import { newBidder } from 'src/adapters/bidderFactory.js';
+import { config } from 'src/config.js';
+import { VIDEO } from '../../../src/mediaTypes.js';
+import { deepClone } from 'src/utils.js';
+
+const ENDPOINT = 'https://hb.minutemedia-prebid.com/hb-mm';
+const TEST_ENDPOINT = 'https://hb.minutemedia-prebid.com/hb-mm-test';
+const TTL = 360;
+
+describe('minutemediaAdapter', function () {
+ const adapter = newBidder(spec);
+
+ describe('inherited functions', function () {
+ it('exists and is a function', function () {
+ expect(adapter.callBids).to.exist.and.to.be.a('function');
+ });
+ });
+
+ describe('isBidRequestValid', function () {
+ const bid = {
+ 'bidder': spec.code,
+ 'adUnitCode': 'adunit-code',
+ 'sizes': [['640', '480']],
+ 'params': {
+ 'org': 'jdye8weeyirk00000001'
+ }
+ };
+
+ it('should return true when required params are passed', function () {
+ expect(spec.isBidRequestValid(bid)).to.equal(true);
+ });
+
+ it('should return false when required params are not found', function () {
+ const newBid = Object.assign({}, bid);
+ delete newBid.params;
+ newBid.params = {
+ 'org': null
+ };
+ expect(spec.isBidRequestValid(newBid)).to.equal(false);
+ });
+ });
+
+ describe('buildRequests', function () {
+ const bidRequests = [
+ {
+ 'bidder': spec.code,
+ 'adUnitCode': 'adunit-code',
+ 'sizes': [[640, 480]],
+ 'params': {
+ 'org': 'jdye8weeyirk00000001'
+ },
+ 'bidId': '299ffc8cca0b87',
+ 'bidderRequestId': '1144f487e563f9',
+ 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d',
+ }
+ ];
+
+ const testModeBidRequests = [
+ {
+ 'bidder': spec.code,
+ 'adUnitCode': 'adunit-code',
+ 'sizes': [[640, 480]],
+ 'params': {
+ 'org': 'jdye8weeyirk00000001',
+ 'testMode': true
+ },
+ 'bidId': '299ffc8cca0b87',
+ 'bidderRequestId': '1144f487e563f9',
+ 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d',
+ }
+ ];
+
+ const bidderRequest = {
+ bidderCode: 'minutemedia',
+ }
+ const placementId = '12345678';
+
+ it('sends the placementId as a query param', function () {
+ bidRequests[0].params.placementId = placementId;
+ const requests = spec.buildRequests(bidRequests, bidderRequest);
+ for (const request of requests) {
+ expect(request.data.placement_id).to.equal(placementId);
+ }
+ });
+
+ it('sends bid request to ENDPOINT via GET', function () {
+ const requests = spec.buildRequests(bidRequests, bidderRequest);
+ for (const request of requests) {
+ expect(request.url).to.equal(ENDPOINT);
+ expect(request.method).to.equal('GET');
+ }
+ });
+
+ it('sends bid request to test ENDPOINT via GET', function () {
+ const requests = spec.buildRequests(testModeBidRequests, bidderRequest);
+ for (const request of requests) {
+ expect(request.url).to.equal(TEST_ENDPOINT);
+ expect(request.method).to.equal('GET');
+ }
+ });
+
+ it('should send the correct bid Id', function () {
+ const requests = spec.buildRequests(bidRequests, bidderRequest);
+ for (const request of requests) {
+ expect(request.data.bid_id).to.equal('299ffc8cca0b87');
+ }
+ });
+
+ it('should send the correct width and height', function () {
+ const requests = spec.buildRequests(bidRequests, bidderRequest);
+ for (const request of requests) {
+ expect(request.data).to.be.an('object');
+ expect(request.data).to.have.property('width', 640);
+ expect(request.data).to.have.property('height', 480);
+ }
+ });
+
+ it('should respect syncEnabled option', function() {
+ config.setConfig({
+ userSync: {
+ syncEnabled: false,
+ filterSettings: {
+ all: {
+ bidders: '*',
+ filter: 'include'
+ }
+ }
+ }
+ });
+ const requests = spec.buildRequests(bidRequests, bidderRequest);
+ for (const request of requests) {
+ expect(request.data).to.be.an('object');
+ expect(request.data).to.not.have.property('cs_method');
+ }
+ });
+
+ it('should respect "iframe" filter settings', function () {
+ config.setConfig({
+ userSync: {
+ syncEnabled: true,
+ filterSettings: {
+ iframe: {
+ bidders: [spec.code],
+ filter: 'include'
+ }
+ }
+ }
+ });
+ const requests = spec.buildRequests(bidRequests, bidderRequest);
+ for (const request of requests) {
+ expect(request.data).to.be.an('object');
+ expect(request.data).to.have.property('cs_method', 'iframe');
+ }
+ });
+
+ it('should respect "all" filter settings', function () {
+ config.setConfig({
+ userSync: {
+ syncEnabled: true,
+ filterSettings: {
+ all: {
+ bidders: [spec.code],
+ filter: 'include'
+ }
+ }
+ }
+ });
+ const requests = spec.buildRequests(bidRequests, bidderRequest);
+ for (const request of requests) {
+ expect(request.data).to.be.an('object');
+ expect(request.data).to.have.property('cs_method', 'iframe');
+ }
+ });
+
+ it('should send the pixel user sync param if userSync is enabled and no "iframe" or "all" configs are present', function () {
+ config.setConfig({
+ userSync: {
+ syncEnabled: true
+ }
+ });
+ const requests = spec.buildRequests(bidRequests, bidderRequest);
+ for (const request of requests) {
+ expect(request.data).to.be.an('object');
+ expect(request.data).to.have.property('cs_method', 'pixel');
+ }
+ });
+
+ it('should respect total exclusion', function() {
+ config.setConfig({
+ userSync: {
+ syncEnabled: true,
+ filterSettings: {
+ image: {
+ bidders: [spec.code],
+ filter: 'exclude'
+ },
+ iframe: {
+ bidders: [spec.code],
+ filter: 'exclude'
+ }
+ }
+ }
+ });
+ const requests = spec.buildRequests(bidRequests, bidderRequest);
+ for (const request of requests) {
+ expect(request.data).to.be.an('object');
+ expect(request.data).to.not.have.property('cs_method');
+ }
+ });
+
+ it('should have us_privacy param if usPrivacy is available in the bidRequest', function () {
+ const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest);
+ const requests = spec.buildRequests(bidRequests, bidderRequestWithUSP);
+ for (const request of requests) {
+ expect(request.data).to.be.an('object');
+ expect(request.data).to.have.property('us_privacy', '1YNN');
+ }
+ });
+
+ it('should have an empty us_privacy param if usPrivacy is missing in the bidRequest', function () {
+ const requests = spec.buildRequests(bidRequests, bidderRequest);
+ for (const request of requests) {
+ expect(request.data).to.be.an('object');
+ expect(request.data).to.not.have.property('us_privacy');
+ }
+ });
+
+ it('should not send the gdpr param if gdprApplies is false in the bidRequest', function () {
+ const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: false}}, bidderRequest);
+ const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR);
+ for (const request of requests) {
+ expect(request.data).to.be.an('object');
+ expect(request.data).to.not.have.property('gdpr');
+ expect(request.data).to.not.have.property('gdpr_consent');
+ }
+ });
+
+ it('should send the gdpr param if gdprApplies is true in the bidRequest', function () {
+ const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: true, consentString: 'test-consent-string'}}, bidderRequest);
+ const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR);
+ for (const request of requests) {
+ expect(request.data).to.be.an('object');
+ expect(request.data).to.have.property('gdpr', true);
+ expect(request.data).to.have.property('gdpr_consent', 'test-consent-string');
+ }
+ });
+
+ it('should have schain param if it is available in the bidRequest', () => {
+ const schain = {
+ ver: '1.0',
+ complete: 1,
+ nodes: [{ asi: 'indirectseller.com', sid: '00001', hp: 1 }],
+ };
+ bidRequests[0].schain = schain;
+ const requests = spec.buildRequests(bidRequests, bidderRequest);
+ for (const request of requests) {
+ expect(request.data).to.be.an('object');
+ expect(request.data).to.have.property('schain', '1.0,1!indirectseller.com,00001,1,,,');
+ }
+ });
+
+ it('should set floor_price to getFloor.floor value if it is greater than params.floorPrice', function() {
+ const bid = deepClone(bidRequests[0]);
+ bid.getFloor = () => {
+ return {
+ currency: 'USD',
+ floor: 3.32
+ }
+ }
+ bid.params.floorPrice = 0.64;
+ const request = spec.buildRequests([bid], bidderRequest)[0];
+ expect(request.data).to.be.an('object');
+ expect(request.data).to.have.property('floor_price', 3.32);
+ });
+
+ it('should set floor_price to params.floorPrice value if it is greater than getFloor.floor', function() {
+ const bid = deepClone(bidRequests[0]);
+ bid.getFloor = () => {
+ return {
+ currency: 'USD',
+ floor: 0.8
+ }
+ }
+ bid.params.floorPrice = 1.5;
+ const request = spec.buildRequests([bid], bidderRequest)[0];
+ expect(request.data).to.be.an('object');
+ expect(request.data).to.have.property('floor_price', 1.5);
+ });
+ });
+
+ describe('interpretResponse', function () {
+ const response = {
+ cpm: 12.5,
+ vastXml: '',
+ width: 640,
+ height: 480,
+ requestId: '21e12606d47ba7',
+ netRevenue: true,
+ currency: 'USD',
+ adomain: ['abc.com']
+ };
+
+ it('should get correct bid response', function () {
+ let expectedResponse = [
+ {
+ requestId: '21e12606d47ba7',
+ cpm: 12.5,
+ width: 640,
+ height: 480,
+ creativeId: '21e12606d47ba7',
+ currency: 'USD',
+ netRevenue: true,
+ ttl: TTL,
+ vastXml: '',
+ mediaType: VIDEO,
+ meta: {
+ advertiserDomains: ['abc.com']
+ }
+ }
+ ];
+ const result = spec.interpretResponse({ body: response });
+ expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0]));
+ });
+ })
+
+ describe('getUserSyncs', function() {
+ const imageSyncResponse = {
+ body: {
+ userSyncPixels: [
+ 'https://image-sync-url.test/1',
+ 'https://image-sync-url.test/2',
+ 'https://image-sync-url.test/3'
+ ]
+ }
+ };
+
+ const iframeSyncResponse = {
+ body: {
+ userSyncURL: 'https://iframe-sync-url.test'
+ }
+ };
+
+ it('should register all img urls from the response', function() {
+ const syncs = spec.getUserSyncs({ pixelEnabled: true }, [imageSyncResponse]);
+ expect(syncs).to.deep.equal([
+ {
+ type: 'image',
+ url: 'https://image-sync-url.test/1'
+ },
+ {
+ type: 'image',
+ url: 'https://image-sync-url.test/2'
+ },
+ {
+ type: 'image',
+ url: 'https://image-sync-url.test/3'
+ }
+ ]);
+ });
+
+ it('should register the iframe url from the response', function() {
+ const syncs = spec.getUserSyncs({ iframeEnabled: true }, [iframeSyncResponse]);
+ expect(syncs).to.deep.equal([
+ {
+ type: 'iframe',
+ url: 'https://iframe-sync-url.test'
+ }
+ ]);
+ });
+
+ it('should register both image and iframe urls from the responses', function() {
+ const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [iframeSyncResponse, imageSyncResponse]);
+ expect(syncs).to.deep.equal([
+ {
+ type: 'iframe',
+ url: 'https://iframe-sync-url.test'
+ },
+ {
+ type: 'image',
+ url: 'https://image-sync-url.test/1'
+ },
+ {
+ type: 'image',
+ url: 'https://image-sync-url.test/2'
+ },
+ {
+ type: 'image',
+ url: 'https://image-sync-url.test/3'
+ }
+ ]);
+ });
+
+ it('should handle an empty response', function() {
+ const syncs = spec.getUserSyncs({ iframeEnabled: true }, []);
+ expect(syncs).to.deep.equal([]);
+ });
+
+ it('should handle when user syncs are disabled', function() {
+ const syncs = spec.getUserSyncs({ pixelEnabled: false }, [imageSyncResponse]);
+ expect(syncs).to.deep.equal([]);
+ });
+ })
+});