diff --git a/modules/bluebillywigBidAdapter.js b/modules/bluebillywigBidAdapter.js
new file mode 100644
index 00000000000..9e4f5b62e48
--- /dev/null
+++ b/modules/bluebillywigBidAdapter.js
@@ -0,0 +1,386 @@
+import * as utils from '../src/utils.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { VIDEO } from '../src/mediaTypes.js';
+import { config } from '../src/config.js';
+import { Renderer } from '../src/Renderer.js';
+import { createEidsArray } from './userId/eids.js';
+
+const DEV_MODE = window.location.search.match(/bbpbs_debug=true/);
+
+// Blue Billywig Constants
+const BB_CONSTANTS = {
+ BIDDER_CODE: 'bluebillywig',
+ AUCTION_URL: '$$URL_STARTpbs.bluebillywig.com/openrtb2/auction?pub=$$PUBLICATION',
+ SYNC_URL: '$$URL_STARTpbs.bluebillywig.com/static/cookie-sync.html?pub=$$PUBLICATION',
+ RENDERER_URL: 'https://$$PUBLICATION.bbvms.com/r/$$RENDERER.js',
+ DEFAULT_TIMEOUT: 5000,
+ DEFAULT_TTL: 300,
+ DEFAULT_WIDTH: 768,
+ DEFAULT_HEIGHT: 432,
+ DEFAULT_NET_REVENUE: true
+};
+
+// Aliasing
+const getConfig = config.getConfig;
+
+// Helper Functions
+export const BB_HELPERS = {
+ addSiteAppDevice: function(request, pageUrl) {
+ if (!request) return;
+
+ if (typeof getConfig('app') === 'object') request.app = getConfig('app');
+ else if (pageUrl) request.site = { page: pageUrl };
+
+ if (typeof getConfig('device') === 'object') request.device = getConfig('device');
+ if (!request.device) request.device = {};
+ if (!request.device.w) request.device.w = window.innerWidth;
+ if (!request.device.h) request.device.h = window.innerHeight;
+ },
+ addSchain: function(request, validBidRequests) {
+ if (!request) return;
+
+ const schain = utils.deepAccess(validBidRequests, '0.schain');
+ if (schain) request.source.ext = { schain: schain };
+ },
+ addCurrency: function(request) {
+ if (!request) return;
+
+ const adServerCur = getConfig('currency.adServerCurrency');
+ if (adServerCur && typeof adServerCur === 'string') request.cur = [adServerCur];
+ else if (Array.isArray(adServerCur) && adServerCur.length) request.cur = [adServerCur[0]];
+ },
+ addUserIds: function(request, validBidRequests) {
+ if (!request) return;
+
+ const bidUserId = utils.deepAccess(validBidRequests, '0.userId');
+ const eids = createEidsArray(bidUserId);
+
+ if (eids.length) {
+ utils.deepSetValue(request, 'user.ext.eids', eids);
+ }
+ },
+ addDigiTrust: function(request, bidRequests) {
+ const digiTrust = BB_HELPERS.getDigiTrustParams(bidRequests && bidRequests[0]);
+ if (digiTrust) utils.deepSetValue(request, 'user.ext.digitrust', digiTrust);
+ },
+ substituteUrl: function (url, publication, renderer) {
+ return url.replace('$$URL_START', (DEV_MODE) ? 'https://dev.' : 'https://').replace('$$PUBLICATION', publication).replace('$$RENDERER', renderer);
+ },
+ getAuctionUrl: function(publication) {
+ return BB_HELPERS.substituteUrl(BB_CONSTANTS.AUCTION_URL, publication);
+ },
+ getSyncUrl: function(publication) {
+ return BB_HELPERS.substituteUrl(BB_CONSTANTS.SYNC_URL, publication);
+ },
+ getRendererUrl: function(publication, renderer) {
+ return BB_HELPERS.substituteUrl(BB_CONSTANTS.RENDERER_URL, publication, renderer);
+ },
+ getDigiTrustParams: function(bidRequest) {
+ const digiTrustId = BB_HELPERS.getDigiTrustId(bidRequest);
+
+ if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) return null;
+ return {
+ id: digiTrustId.id,
+ keyv: digiTrustId.keyv
+ }
+ },
+ getDigiTrustId: function(bidRequest) {
+ const bidRequestDigiTrust = utils.deepAccess(bidRequest, 'userId.digitrustid.data');
+ if (bidRequestDigiTrust) return bidRequestDigiTrust;
+
+ const digiTrustUser = getConfig('digiTrustId');
+ return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null;
+ },
+ transformRTBToPrebidProps: function(bid, serverResponse) {
+ bid.cpm = bid.price; delete bid.price;
+ bid.bidId = bid.impid;
+ bid.requestId = bid.impid; delete bid.impid;
+ bid.width = bid.w || BB_CONSTANTS.DEFAULT_WIDTH;
+ bid.height = bid.h || BB_CONSTANTS.DEFAULT_HEIGHT;
+ if (bid.adm) {
+ bid.ad = bid.adm;
+ bid.vastXml = bid.adm;
+ delete bid.adm;
+ }
+ if (bid.nurl && !bid.adm) { // ad markup is on win notice url, and adm is ommited according to OpenRTB 2.5
+ bid.vastUrl = bid.nurl;
+ delete bid.nurl;
+ }
+ bid.netRevenue = BB_CONSTANTS.DEFAULT_NET_REVENUE;
+ bid.creativeId = bid.crid; delete bid.crid;
+ bid.currency = serverResponse.cur;
+ bid.ttl = BB_CONSTANTS.DEFAULT_TTL;
+ },
+};
+
+// Renderer Functions
+const BB_RENDERER = {
+ bootstrapPlayer: function(bid) {
+ const config = {
+ code: bid.adUnitCode,
+ };
+
+ if (bid.vastXml) config.vastXml = bid.vastXml;
+ else if (bid.vastUrl) config.vastUrl = bid.vastUrl;
+
+ if (!bid.vastXml && !bid.vastUrl) {
+ utils.logWarn(`${BB_CONSTANTS.BIDDER_CODE}: No vastXml or vastUrl on bid, bailing...`);
+ return;
+ }
+
+ const rendererId = BB_RENDERER.getRendererId(bid.publicationName, bid.rendererCode);
+
+ const ele = document.getElementById(bid.adUnitCode); // NB convention
+
+ let renderer;
+
+ for (let rendererIndex = 0; rendererIndex < window.bluebillywig.renderers.length; rendererIndex++) {
+ if (window.bluebillywig.renderers[rendererIndex]._id === rendererId) {
+ renderer = window.bluebillywig.renderers[rendererIndex];
+ break;
+ }
+ }
+
+ if (renderer) renderer.bootstrap(config, ele);
+ else utils.logWarn(`${BB_CONSTANTS.BIDDER_CODE}: Couldn't find a renderer with ${rendererId}`);
+ },
+ newRenderer: function(rendererUrl, adUnitCode) {
+ const renderer = Renderer.install({
+ url: rendererUrl,
+ loaded: false,
+ adUnitCode
+ });
+
+ try {
+ renderer.setRender(BB_RENDERER.outstreamRender);
+ } catch (err) {
+ utils.logWarn(`${BB_CONSTANTS.BIDDER_CODE}: Error tying to setRender on renderer`, err);
+ }
+
+ return renderer;
+ },
+ outstreamRender: function(bid) {
+ bid.renderer.push(function() { BB_RENDERER.bootstrapPlayer(bid) });
+ },
+ getRendererId: function(pub, renderer) {
+ return `${pub}-${renderer}`; // NB convention!
+ }
+};
+
+// Spec Functions
+// These functions are used to construct the core spec for the adapter
+export const spec = {
+ code: BB_CONSTANTS.BIDDER_CODE,
+ supportedMediaTypes: [VIDEO],
+ syncStore: { bidders: [], },
+ isBidRequestValid(bid) {
+ const publicationNameRegex = /^\w+\.?\w+$/;
+ const rendererRegex = /^[\w+_]+$/;
+
+ if (!bid.params) {
+ utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no params set on bid. Rejecting bid: `, bid);
+ return false;
+ }
+
+ if (!bid.params.hasOwnProperty('publicationName') || typeof bid.params.publicationName !== 'string') {
+ utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no publicationName specified in bid params, or it's not a string. Rejecting bid: `, bid);
+ return false;
+ } else if (!publicationNameRegex.test(bid.params.publicationName)) {
+ utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: publicationName must be in format 'publication' or 'publication.environment'. Rejecting bid: `, bid);
+ return false;
+ }
+
+ if ((!bid.params.hasOwnProperty('rendererCode') || typeof bid.params.rendererCode !== 'string')) {
+ utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no rendererCode was specified in bid params. Rejecting bid: `, bid);
+ return false;
+ } else if (!rendererRegex.test(bid.params.rendererCode)) {
+ utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: rendererCode must be alphanumeric, including underscores. Rejecting bid: `, bid);
+ return false;
+ }
+
+ if (!bid.params.accountId) {
+ utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no accountId specified in bid params. Rejecting bid: `, bid);
+ return false;
+ }
+
+ if (bid.params.hasOwnProperty('connections')) {
+ if (!Array.isArray(bid.params.connections)) {
+ utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: connections is not of type array. Rejecting bid: `, bid);
+ return false;
+ } else {
+ for (let connectionIndex = 0; connectionIndex < bid.params.connections.length; connectionIndex++) {
+ const connection = bid.params.connections[connectionIndex];
+ if (!bid.params.hasOwnProperty(connection)) {
+ utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: connection specified in params.connections, but not configured in params. Rejecting bid: `, bid);
+ return false;
+ }
+ }
+ }
+ } else {
+ utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no connections specified in bid. Rejecting bid: `, bid);
+ return false;
+ }
+
+ if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) {
+ if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) {
+ utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no context specified in bid. Rejecting bid: `, bid);
+ return false;
+ }
+
+ if (bid.mediaTypes[VIDEO].context !== 'outstream') {
+ utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: video.context is invalid, must be "outstream". Rejecting bid: `, bid);
+ return false;
+ }
+ } else {
+ utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: mediaTypes or mediaTypes.video is not specified. Rejecting bid: `, bid);
+ return false;
+ }
+
+ return true;
+ },
+ buildRequests(validBidRequests, bidderRequest) {
+ const imps = [];
+
+ for (let validBidRequestIndex = 0; validBidRequestIndex < validBidRequests.length; validBidRequestIndex++) {
+ const validBidRequest = validBidRequests[validBidRequestIndex];
+ const _this = this;
+
+ const ext = validBidRequest.params.connections.reduce(function(extBuilder, connection) {
+ extBuilder[connection] = validBidRequest.params[connection];
+
+ if (_this.syncStore.bidders.indexOf(connection) === -1) _this.syncStore.bidders.push(connection);
+
+ return extBuilder;
+ }, {});
+
+ imps.push({ id: validBidRequest.bidId, ext, secure: window.location.protocol === 'https' ? 1 : 0, video: utils.deepAccess(validBidRequest, 'mediaTypes.video') });
+ }
+
+ const request = {
+ id: bidderRequest.auctionId,
+ source: {tid: bidderRequest.auctionId},
+ tmax: BB_CONSTANTS.DEFAULT_TIMEOUT,
+ imp: imps,
+ test: DEV_MODE ? 1 : 0,
+ ext: {
+ prebid: {
+ targeting: { includewinners: true, includebidderkeys: false }
+ }
+ }
+ };
+
+ // handle privacy settings for GDPR/CCPA/COPPA
+ if (bidderRequest.gdprConsent) {
+ let gdprApplies = 0;
+ if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0;
+ utils.deepSetValue(request, 'regs.ext.gdpr', gdprApplies);
+ utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString);
+ }
+
+ if (bidderRequest.uspConsent) {
+ utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent);
+ this.syncStore.uspConsent = bidderRequest.uspConsent;
+ }
+
+ if (getConfig('coppa') == true) utils.deepSetValue(request, 'regs.coppa', 1);
+
+ // Enrich the request with any external data we may have
+ BB_HELPERS.addSiteAppDevice(request, bidderRequest.refererInfo && bidderRequest.refererInfo.referer);
+ BB_HELPERS.addSchain(request, validBidRequests);
+ BB_HELPERS.addCurrency(request);
+ BB_HELPERS.addUserIds(request, validBidRequests);
+ BB_HELPERS.addDigiTrust(request, validBidRequests);
+
+ return {
+ method: 'POST',
+ url: BB_HELPERS.getAuctionUrl(validBidRequests[0].params.publicationName),
+ data: JSON.stringify(request),
+ bidderRequest: bidderRequest
+ };
+ },
+ interpretResponse(serverResponse, request) {
+ serverResponse = serverResponse.body || {};
+
+ if (!serverResponse.hasOwnProperty('seatbid') || !Array.isArray(serverResponse.seatbid)) {
+ return [];
+ }
+
+ const bids = [];
+
+ for (let seatbidIndex = 0; seatbidIndex < serverResponse.seatbid.length; seatbidIndex++) {
+ const seatbid = serverResponse.seatbid[seatbidIndex];
+ if (!seatbid.bid || !Array.isArray(seatbid.bid)) continue;
+ for (let bidIndex = 0; bidIndex < seatbid.bid.length; bidIndex++) {
+ const bid = seatbid.bid[bidIndex];
+ BB_HELPERS.transformRTBToPrebidProps(bid, serverResponse);
+
+ let bidParams;
+ for (let bidderRequestBidsIndex = 0; bidderRequestBidsIndex < request.bidderRequest.bids.length; bidderRequestBidsIndex++) {
+ if (request.bidderRequest.bids[bidderRequestBidsIndex].bidId === bid.bidId) {
+ bidParams = request.bidderRequest.bids[bidderRequestBidsIndex].params;
+ }
+ }
+
+ if (bidParams) {
+ bid.publicationName = bidParams.publicationName;
+ bid.rendererCode = bidParams.rendererCode;
+ bid.accountId = bidParams.accountId;
+ }
+
+ const rendererUrl = BB_HELPERS.getRendererUrl(bid.publicationName, bid.rendererCode);
+
+ bid.renderer = BB_RENDERER.newRenderer(rendererUrl, bid.adUnitCode);
+
+ bids.push(bid);
+ }
+ }
+
+ return bids;
+ },
+ getUserSyncs(syncOptions, serverResponses, gdpr) {
+ if (!serverResponses || !serverResponses.length) return [];
+ if (!syncOptions.iframeEnabled) return [];
+
+ const queryString = [];
+ let accountId;
+ let publication;
+
+ const serverResponse = serverResponses[0];
+ if (!serverResponse.body || !serverResponse.body.seatbid) return [];
+
+ for (let seatbidIndex = 0; seatbidIndex < serverResponse.body.seatbid.length; seatbidIndex++) {
+ const seatbid = serverResponse.body.seatbid[seatbidIndex];
+ for (let bidIndex = 0; bidIndex < seatbid.bid.length; bidIndex++) {
+ const bid = seatbid.bid[bidIndex];
+ accountId = bid.accountId || null;
+ publication = bid.publicationName || null;
+
+ if (publication && accountId) break;
+ }
+ if (publication && accountId) break;
+ }
+
+ if (!publication || !accountId) return [];
+
+ if (gdpr.gdprApplies) queryString.push(`gdpr=${gdpr.gdprApplies ? 1 : 0}`);
+ if (gdpr.gdprApplies && gdpr.consentString) queryString.push(`gdpr_consent=${gdpr.consentString}`);
+
+ if (this.syncStore.uspConsent) queryString.push(`usp_consent=${this.syncStore.uspConsent}`);
+
+ queryString.push(`accountId=${accountId}`);
+ queryString.push(`bidders=${btoa(JSON.stringify(this.syncStore.bidders))}`);
+ queryString.push(`cb=${Date.now()}-${Math.random().toString().replace('.', '')}`);
+
+ if (DEV_MODE) queryString.push('bbpbs_debug=true');
+
+ // NB syncUrl by default starts with ?pub=$$PUBLICATION
+ const syncUrl = `${BB_HELPERS.getSyncUrl(publication)}&${queryString.join('&')}`;
+
+ return [{
+ type: 'iframe',
+ url: syncUrl
+ }];
+ }
+};
+
+registerBidder(spec);
diff --git a/modules/bluebillywigBidAdapter.md b/modules/bluebillywigBidAdapter.md
new file mode 100644
index 00000000000..7879697baf5
--- /dev/null
+++ b/modules/bluebillywigBidAdapter.md
@@ -0,0 +1,38 @@
+# Overview
+
+```
+Module Name: Blue Billywig Adapter
+Module Type: Bidder Adapter
+Maintainer: dev+prebid@bluebillywig.com
+```
+
+# Description
+
+Prebid Blue Billywig Bidder Adapter
+
+# Test Parameters
+
+```
+ const adUnits = [{
+ code: 'ad-unit',
+ sizes: [[[768,432],[640,480],[640,360]]],
+ mediaTypes: {
+ video: {
+ playerSize: [768, 432],
+ context: 'outstream',
+ mimes: ['video/mp4'],
+ protocols: [ 2,3,5,6]
+ }
+ },
+ bids: [{
+ bidder: 'bluebillywig',
+ params: {
+ publicationName: "bbprebid",
+ rendererCode: "renderer",
+ accountId: 642,
+ connections: [ 'bluebillywig' ],
+ bluebillywig: {}
+ }
+ }]
+ }];
+```
diff --git a/test/spec/modules/bluebillywigBidAdapter_spec.js b/test/spec/modules/bluebillywigBidAdapter_spec.js
new file mode 100644
index 00000000000..fcf3e16ad8b
--- /dev/null
+++ b/test/spec/modules/bluebillywigBidAdapter_spec.js
@@ -0,0 +1,898 @@
+import { expect } from 'chai';
+import { spec } from 'modules/bluebillywigBidAdapter.js';
+import * as bidderFactory from 'src/adapters/bidderFactory.js';
+import { auctionManager } from 'src/auctionManager.js';
+import { deepClone, deepAccess } from 'src/utils.js';
+import { config } from 'src/config.js';
+import { VIDEO } from 'src/mediaTypes.js';
+
+const BB_CONSTANTS = {
+ BIDDER_CODE: 'bluebillywig',
+ AUCTION_URL: '$$URL_STARTpbs.bluebillywig.com/openrtb2/auction?pub=$$PUBLICATION',
+ SYNC_URL: '$$URL_STARTpbs.bluebillywig.com/static/cookie-sync.html?pub=$$PUBLICATION',
+ RENDERER_URL: 'https://$$PUBLICATION.bbvms.com/r/$$RENDERER.js',
+ DEFAULT_TIMEOUT: 5000,
+ DEFAULT_TTL: 300,
+ DEFAULT_WIDTH: 768,
+ DEFAULT_HEIGHT: 432,
+ DEFAULT_NET_REVENUE: true
+};
+
+describe('BlueBillywigAdapter', () => {
+ describe('isBidRequestValid', () => {
+ const baseValidBid = {
+ bidder: BB_CONSTANTS.BIDDER_CODE,
+ params: {
+ accountId: 123,
+ publicationName: 'bbprebid.dev',
+ rendererCode: 'glorious_renderer',
+ connections: [ BB_CONSTANTS.BIDDER_CODE ],
+ bluebillywig: {}
+ },
+ mediaTypes: {
+ video: {
+ context: 'outstream'
+ }
+ }
+ };
+
+ it('should return true when required params found', () => {
+ expect(spec.isBidRequestValid(baseValidBid)).to.equal(true);
+ });
+
+ it('should return false when publicationName is missing', () => {
+ const bid = deepClone(baseValidBid);
+ delete bid.params.publicationName;
+
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+
+ it('should return false when publicationName is not a string', () => {
+ const bid = deepClone(baseValidBid);
+
+ bid.params.publicationName = 123;
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+
+ bid.params.publicationName = false;
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+
+ bid.params.publicationName = void (0);
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+
+ bid.params.publicationName = {};
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+
+ it('should return false when publicationName is formatted poorly', () => {
+ const bid = deepClone(baseValidBid);
+
+ bid.params.publicationName = 'bb.';
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+
+ bid.params.publicationName = 'bb-test';
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+
+ bid.params.publicationName = '?';
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+
+ it('should return false when renderer is not specified', () => {
+ const bid = deepClone(baseValidBid);
+
+ delete bid.params.rendererCode;
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+
+ it('should return false when renderer is not a string', () => {
+ const bid = deepClone(baseValidBid);
+
+ bid.params.rendererCode = 123;
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+
+ bid.params.rendererCode = false;
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+
+ bid.params.rendererCode = void (0);
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+
+ bid.params.rendererCode = {};
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+
+ it('should return false when renderer is formatted poorly', () => {
+ const bid = deepClone(baseValidBid);
+
+ bid.params.rendererCode = 'bb.';
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+
+ bid.params.rendererCode = 'bb-test';
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+
+ bid.params.rendererCode = '?';
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+
+ it('should return false when accountId is not specified', () => {
+ const bid = deepClone(baseValidBid);
+
+ delete bid.params.accountId;
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+
+ it('should return false when connections is not specified', () => {
+ const bid = deepClone(baseValidBid);
+
+ delete bid.params.connections;
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+
+ it('should return false when connections is not an array', () => {
+ const bid = deepClone(baseValidBid);
+
+ bid.params.connections = 123;
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+
+ bid.params.connections = false;
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+
+ bid.params.connections = void (0);
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+
+ bid.params.connections = {};
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+
+ bid.params.connections = 'string';
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+
+ it('should return false when a connection is missing', () => {
+ const bid = deepClone(baseValidBid);
+
+ bid.params.connections.push('potatoes');
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+
+ bid.params.connections.pop();
+
+ delete bid.params[BB_CONSTANTS.BIDDER_CODE];
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+
+ it('should fail if bid has no mediaTypes', () => {
+ const bid = deepClone(baseValidBid);
+
+ delete bid.mediaTypes;
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+
+ it('should fail if bid has no mediaTypes.video', () => {
+ const bid = deepClone(baseValidBid);
+
+ delete bid.mediaTypes[VIDEO];
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+
+ it('should fail if bid has no mediaTypes.video.context', () => {
+ const bid = deepClone(baseValidBid);
+
+ delete bid.mediaTypes[VIDEO].context;
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+
+ it('should fail if mediaTypes.video.context is not "outstream"', () => {
+ const bid = deepClone(baseValidBid);
+
+ bid.mediaTypes[VIDEO].context = 'instream';
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+ });
+
+ describe('buildRequests', () => {
+ const publicationName = 'bbprebid.dev';
+ const rendererCode = 'glorious_renderer';
+
+ const baseValidBid = {
+ bidder: BB_CONSTANTS.BIDDER_CODE,
+ params: {
+ accountId: 123,
+ publicationName: publicationName,
+ rendererCode: rendererCode,
+ connections: [ BB_CONSTANTS.BIDDER_CODE ],
+ bluebillywig: {}
+ },
+ mediaTypes: {
+ video: {
+ context: 'outstream'
+ }
+ }
+ };
+
+ const baseValidBidRequests = [baseValidBid];
+
+ const validBidderRequest = {
+ auctionId: '12abc345-67d8-9012-e345-6f78901a2b34',
+ auctionStart: 1585918458868,
+ bidderCode: BB_CONSTANTS.BIDDER_CODE,
+ bidderRequestId: '1a2345b67c8d9e0',
+ bids: [{
+ adUnitCode: 'ad-unit-test',
+ auctionId: '12abc345-67d8-9012-e345-6f78901a2b34',
+ bidId: '1234ab567c89de0',
+ bidRequestsCount: 1,
+ bidder: BB_CONSTANTS.BIDDER_CODE,
+ bidderRequestId: '1a2345b67c8d9e0',
+ params: baseValidBid.params,
+ sizes: [[768, 432], [640, 480], [630, 360]],
+ transactionId: '2b34c5de-f67a-8901-bcd2-34567efabc89'
+ }],
+ start: 11585918458869,
+ timeout: 3000
+ };
+
+ it('sends bid request to AUCTION_URL via POST', () => {
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ expect(request.url).to.equal(`https://pbs.bluebillywig.com/openrtb2/auction?pub=${publicationName}`);
+ expect(request.method).to.equal('POST');
+ });
+
+ it('sends data as a string', () => {
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ expect(request.data).to.be.a('string');
+ });
+
+ it('sends all bid parameters', () => {
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']);
+ });
+
+ it('builds the base request properly', () => {
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload.id).to.equal(validBidderRequest.auctionId);
+ expect(payload.source).to.be.an('object');
+ expect(payload.source.tid).to.equal(validBidderRequest.auctionId);
+ expect(payload.tmax).to.equal(BB_CONSTANTS.DEFAULT_TIMEOUT);
+ expect(payload.imp).to.be.an('array');
+ expect(payload.test).to.be.a('number');
+ expect(payload).to.have.nested.property('ext.prebid.targeting');
+ expect(payload.ext.prebid.targeting).to.be.an('object');
+ expect(payload.ext.prebid.targeting.includewinners).to.equal(true);
+ expect(payload.ext.prebid.targeting.includebidderkeys).to.equal(false);
+ });
+
+ it('adds an impression to the payload', () => {
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload.imp.length).to.equal(1);
+ });
+
+ it('adds connections to ext', () => {
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload.imp[0].ext).to.have.all.keys(['bluebillywig']);
+ });
+
+ it('adds gdpr when present', () => {
+ const newValidBidderRequest = deepClone(validBidderRequest);
+ newValidBidderRequest.gdprConsent = {
+ consentString: 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA',
+ gdprApplies: true
+ };
+
+ const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload).to.have.nested.property('regs.ext.gdpr');
+ expect(payload.regs.ext.gdpr).to.be.a('number');
+ expect(payload.regs.ext.gdpr).to.equal(1);
+ expect(payload).to.have.nested.property('user.ext.consent');
+ expect(payload.user.ext.consent).to.equal(newValidBidderRequest.gdprConsent.consentString);
+ });
+
+ it('sets gdpr to 0 when explicitly gdprApplies: false', () => {
+ const newValidBidderRequest = deepClone(validBidderRequest);
+ newValidBidderRequest.gdprConsent = {
+ gdprApplies: false
+ };
+
+ const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload).to.have.nested.property('regs.ext.gdpr');
+ expect(payload.regs.ext.gdpr).to.be.a('number');
+ expect(payload.regs.ext.gdpr).to.equal(0);
+ });
+
+ it('adds usp_consent when present', () => {
+ const newValidBidderRequest = deepClone(validBidderRequest);
+ newValidBidderRequest.uspConsent = '1YYY';
+
+ const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload).to.have.nested.property('regs.ext.us_privacy');
+ expect(payload.regs.ext.us_privacy).to.equal(newValidBidderRequest.uspConsent);
+ });
+
+ it('sets coppa to 1 when specified in config', () => {
+ config.setConfig({'coppa': true});
+
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload).to.have.nested.property('regs.coppa');
+ expect(payload.regs.coppa).to.equal(1);
+
+ config.resetConfig();
+ });
+
+ it('does not set coppa when disabled in the config', () => {
+ config.setConfig({'coppa': false});
+
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(deepAccess(payload, 'regs.coppa')).to.be.undefined;
+
+ config.resetConfig();
+ });
+
+ it('does not set coppa when not specified in config', () => {
+ config.resetConfig();
+
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(deepAccess(payload, 'regs.coppa')).to.be.undefined;
+ });
+
+ it('should add window size to request by default', () => {
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload).to.have.nested.property('device.w');
+ expect(payload).to.have.nested.property('device.h');
+ expect(payload.device.w).to.be.a('number');
+ expect(payload.device.h).to.be.a('number');
+ });
+
+ it('should add app when specified in config', () => {
+ config.setConfig({ app: { bundle: 'org.prebid.mobile.demoapp', domain: 'prebid.org' } });
+
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload).to.have.property('app');
+ expect(payload).to.have.nested.property('app.bundle');
+ expect(payload).to.have.nested.property('app.domain');
+ expect(payload.app.bundle).to.equal('org.prebid.mobile.demoapp');
+ expect(payload.app.domain).to.equal('prebid.org');
+
+ config.resetConfig();
+ });
+
+ it('should add referrerInfo as site when no app is set', () => {
+ const newValidBidderRequest = deepClone(validBidderRequest);
+
+ newValidBidderRequest.refererInfo = { referer: 'https://www.bluebillywig.com' };
+
+ const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload).to.have.nested.property('site.page');
+ expect(payload.site.page).to.equal('https://www.bluebillywig.com');
+ });
+
+ it('should not add referrerInfo as site when app is set', () => {
+ config.setConfig({ app: { bundle: 'org.prebid.mobile.demoapp', domain: 'prebid.org' } });
+
+ const newValidBidderRequest = deepClone(validBidderRequest);
+ newValidBidderRequest.refererInfo = { referer: 'https://www.bluebillywig.com' };
+
+ const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload.site).to.be.undefined;
+ config.resetConfig();
+ });
+
+ it('should add device size to request when specified in config', () => {
+ config.setConfig({ device: { w: 1, h: 1 } });
+
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload).to.have.nested.property('device.w');
+ expect(payload).to.have.nested.property('device.h');
+ expect(payload.device.w).to.be.a('number');
+ expect(payload.device.h).to.be.a('number');
+ expect(payload.device.w).to.equal(1);
+ expect(payload.device.h).to.equal(1);
+
+ config.resetConfig();
+ });
+
+ it('should set schain on the request when set on config', () => {
+ const schain = {
+ validation: 'lax',
+ config: {
+ ver: '1.0',
+ complete: 1,
+ nodes: [
+ {
+ asi: 'indirectseller.com',
+ sid: '00001',
+ hp: 1
+ }
+ ]
+ }
+ };
+
+ const newBaseValidBidRequests = deepClone(baseValidBidRequests);
+ newBaseValidBidRequests[0].schain = schain;
+
+ const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload).to.have.nested.property('source.ext.schain');
+ expect(payload.source.ext.schain).to.deep.equal(schain);
+ });
+
+ it('should add currency when specified on the config', () => {
+ config.setConfig({ currency: { adServerCurrency: 'USD' } });
+
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload).to.have.property('cur');
+ expect(payload.cur).to.eql(['USD']); // NB not equal, eql to check for same array because [1] === [1] fails normally
+
+ config.resetConfig();
+ });
+
+ it('should also take in array for currency on the config', () => {
+ config.setConfig({ currency: { adServerCurrency: ['USD', 'PHP'] } });
+
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload).to.have.property('cur');
+ expect(payload.cur).to.eql(['USD']); // NB not equal, eql to check for same array because [1] === [1] fails normally
+
+ config.resetConfig();
+ });
+
+ it('should not set cur when currency is not specified on the config', () => {
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload.cur).to.be.undefined;
+ });
+
+ it('should set user ids when present', () => {
+ const userId = { tdid: 123 };
+
+ const newBaseValidBidRequests = deepClone(baseValidBidRequests);
+ newBaseValidBidRequests[0].userId = { criteoId: 'sample-userid' };
+
+ const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload).to.have.nested.property('user.ext.eids');
+ expect(payload.user.ext.eids).to.be.an('array');
+ expect(payload.user.ext.eids.length).to.equal(1);
+ });
+
+ it('should not set user ids when none present', () => {
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(deepAccess(payload, 'user.ext.eids')).to.be.undefined;
+ });
+
+ it('should set digitrust when present on bid', () => {
+ const digiTrust = {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}};
+
+ const newBaseValidBidRequests = deepClone(baseValidBidRequests);
+ newBaseValidBidRequests[0].userId = { digitrustid: digiTrust };
+
+ const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload).to.have.nested.property('user.ext.digitrust');
+ expect(payload.user.ext.digitrust.id).to.equal(digiTrust.data.id);
+ expect(payload.user.ext.digitrust.keyv).to.equal(digiTrust.data.keyv);
+ });
+
+ it('should not set digitrust when opted out', () => {
+ const digiTrust = {data: {id: 'DTID', keyv: 4, privacy: {optout: true}, producer: 'ABC', version: 2}};
+
+ const newBaseValidBidRequests = deepClone(baseValidBidRequests);
+ newBaseValidBidRequests[0].userId = { digitrustid: digiTrust };
+
+ const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(deepAccess(payload, 'user.ext.digitrust')).to.be.undefined;
+ });
+ });
+ describe('interpretResponse', () => {
+ const publicationName = 'bbprebid.dev';
+ const rendererCode = 'glorious_renderer';
+
+ const baseValidBid = {
+ bidder: BB_CONSTANTS.BIDDER_CODE,
+ params: {
+ accountId: 123,
+ publicationName: publicationName,
+ rendererCode: rendererCode,
+ connections: [ BB_CONSTANTS.BIDDER_CODE ],
+ bluebillywig: {}
+ },
+ mediaTypes: {
+ video: {
+ context: 'outstream'
+ }
+ }
+ };
+
+ const baseValidBidRequests = [baseValidBid];
+
+ const validBidderRequest = {
+ auctionId: '12abc345-67d8-9012-e345-6f78901a2b34',
+ auctionStart: 1585918458868,
+ bidderCode: BB_CONSTANTS.BIDDER_CODE,
+ bidderRequestId: '1a2345b67c8d9e0',
+ bids: [{
+ adUnitCode: 'ad-unit-test',
+ auctionId: '12abc345-67d8-9012-e345-6f78901a2b34',
+ bidId: '1234ab567c89de0',
+ bidRequestsCount: 1,
+ bidder: BB_CONSTANTS.BIDDER_CODE,
+ bidderRequestId: '1a2345b67c8d9e0',
+ params: baseValidBid.params,
+ sizes: [[768, 432], [640, 480], [630, 360]],
+ transactionId: '2b34c5de-f67a-8901-bcd2-34567efabc89'
+ }],
+ start: 11585918458869,
+ timeout: 3000
+ };
+
+ const validResponse = {
+ id: 'a12abc345-67d8-9012-e345-6f78901a2b34',
+ seatbid: [
+ {
+ bid: [
+ {
+ id: '1',
+ impid: '1234ab567c89de0',
+ price: 1,
+ adm: '\r\nBB Adserver00:00:51',
+ adid: '67069817',
+ adomain: [
+ 'bluebillywig.com'
+ ],
+ cid: '3535',
+ crid: '67069817',
+ w: 1,
+ h: 1,
+ publicationName: 'bbprebid',
+ accountId: 123,
+ ext: {
+ prebid: {
+ targeting: {
+ hb_bidder: 'bluebillywig',
+ hb_pb: '1.00',
+ hb_size: '1x1'
+ },
+ type: 'video'
+ },
+ bidder: {
+ prebid: {
+ targeting: {
+ hb_bidder: 'bluebillywig',
+ hb_pb: '10.00',
+ hb_size: '1x1'
+ },
+ type: 'video',
+ video: {
+ duration: 51,
+ primary_category: ''
+ }
+ },
+ bidder: {
+ bluebillywig: {
+ brand_id: 1,
+ auction_id: 1,
+ bid_ad_type: 1,
+ creative_info: {
+ video: {
+ duration: 51,
+ mimes: [
+ 'video/x-flv',
+ 'video/mp4',
+ 'video/webm'
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ ],
+ seat: 'bluebillywig'
+ }
+ ],
+ cur: 'USD',
+ ext: {
+ responsetimemillis: {
+ bluebillywig: 0
+ },
+ tmaxrequest: 5000
+ }
+ };
+
+ const serverResponse = { body: validResponse };
+
+ it('should build bid array', () => {
+ const response = deepClone(serverResponse);
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const result = spec.interpretResponse(response, request);
+
+ expect(result.length).to.equal(1);
+ });
+
+ it('should have all relevant fields', () => {
+ const response = deepClone(serverResponse);
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const result = spec.interpretResponse(response, request);
+ const bid = result[0];
+
+ // BB_HELPERS.transformRTBToPrebidProps
+ expect(bid.cpm).to.equal(serverResponse.body.seatbid[0].bid[0].price);
+ expect(bid.bidId).to.equal(serverResponse.body.seatbid[0].bid[0].impid);
+ expect(bid.requestId).to.equal(serverResponse.body.seatbid[0].bid[0].impid);
+ expect(bid.width).to.equal(serverResponse.body.seatbid[0].bid[0].w || BB_CONSTANTS.DEFAULT_WIDTH);
+ expect(bid.height).to.equal(serverResponse.body.seatbid[0].bid[0].h || BB_CONSTANTS.DEFAULT_HEIGHT);
+ expect(bid.ad).to.equal(serverResponse.body.seatbid[0].bid[0].adm);
+ expect(bid.netRevenue).to.equal(BB_CONSTANTS.DEFAULT_NET_REVENUE);
+ expect(bid.creativeId).to.equal(serverResponse.body.seatbid[0].bid[0].crid);
+ expect(bid.currency).to.equal(serverResponse.body.cur);
+ expect(bid.ttl).to.equal(BB_CONSTANTS.DEFAULT_TTL);
+
+ expect(bid.publicationName).to.equal(validBidderRequest.bids[0].params.publicationName);
+ expect(bid.rendererCode).to.equal(validBidderRequest.bids[0].params.rendererCode);
+ expect(bid.accountId).to.equal(validBidderRequest.bids[0].params.accountId);
+ });
+
+ it('should not give anything when seatbid is an empty array', () => {
+ const seatbidEmptyArray = deepClone(serverResponse);
+ seatbidEmptyArray.body.seatbid = [];
+
+ const response = seatbidEmptyArray;
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const result = spec.interpretResponse(response, request);
+
+ expect(result.length).to.equal(0);
+ });
+
+ it('should not give anything when seatbid is missing', () => {
+ const seatbidMissing = deepClone(serverResponse);
+ delete seatbidMissing.body.seatbid;
+
+ const response = seatbidMissing;
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const result = spec.interpretResponse(response, request);
+
+ expect(result.length).to.equal(0);
+ });
+
+ const seatbidNotArrayResponse = deepClone(serverResponse);
+ it('should not give anything when seatbid is not an array', () => {
+ const invalidValues = [ false, null, {}, void (0), 123, 'string' ];
+
+ for (const invalidValue of invalidValues) {
+ seatbidNotArrayResponse.body.seatbid = invalidValue
+ const response = deepClone(seatbidNotArrayResponse); // interpretResponse is destructive
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const result = spec.interpretResponse(response, request);
+
+ expect(result.length).to.equal(0);
+ }
+ });
+
+ it('should not give anything when seatbid.bid is an empty array', () => {
+ const seatbidBidEmpty = deepClone(serverResponse);
+ seatbidBidEmpty.body.seatbid[0].bid = [];
+
+ const response = seatbidBidEmpty;
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const result = spec.interpretResponse(response, request);
+
+ expect(result.length).to.equal(0);
+ });
+
+ it('should not give anything when seatbid.bid is missing', () => {
+ const seatbidBidMissing = deepClone(serverResponse);
+ delete seatbidBidMissing.body.seatbid[0].bid;
+
+ const response = seatbidBidMissing;
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const result = spec.interpretResponse(response, request);
+
+ expect(result.length).to.equal(0);
+ });
+
+ it('should not give anything when seatbid.bid is not an array', () => {
+ const seatbidBidNotArray = deepClone(serverResponse);
+
+ const invalidValues = [ false, null, {}, void (0), 123, 'string' ];
+
+ for (const invalidValue of invalidValues) {
+ seatbidBidNotArray.body.seatbid[0].bid = invalidValue;
+
+ const response = deepClone(seatbidBidNotArray); // interpretResponse is destructive
+ const request = spec.buildRequests(baseValidBidRequests, validBidderRequest);
+ const result = spec.interpretResponse(response, request);
+
+ expect(result.length).to.equal(0);
+ }
+ });
+ });
+ describe('getUserSyncs', () => {
+ const publicationName = 'bbprebid.dev';
+ const rendererCode = 'glorious_renderer';
+
+ const baseValidBid = {
+ bidder: BB_CONSTANTS.BIDDER_CODE,
+ params: {
+ accountId: 123,
+ publicationName: publicationName,
+ rendererCode: rendererCode,
+ connections: [ BB_CONSTANTS.BIDDER_CODE ],
+ bluebillywig: {}
+ },
+ mediaTypes: {
+ video: {
+ context: 'outstream'
+ }
+ }
+ };
+
+ const validBidRequests = [baseValidBid];
+
+ const validBidderRequest = {
+ auctionId: '12abc345-67d8-9012-e345-6f78901a2b34',
+ auctionStart: 1585918458868,
+ bidderCode: BB_CONSTANTS.BIDDER_CODE,
+ bidderRequestId: '1a2345b67c8d9e0',
+ bids: [{
+ adUnitCode: 'ad-unit-test',
+ auctionId: '12abc345-67d8-9012-e345-6f78901a2b34',
+ bidId: '1234ab567c89de0',
+ bidRequestsCount: 1,
+ bidder: BB_CONSTANTS.BIDDER_CODE,
+ bidderRequestId: '1a2345b67c8d9e0',
+ params: baseValidBid.params,
+ sizes: [[768, 432], [640, 480], [630, 360]],
+ transactionId: '2b34c5de-f67a-8901-bcd2-34567efabc89'
+ }],
+ start: 11585918458869,
+ timeout: 3000
+ };
+ const validResponse = {
+ id: 'a12abc345-67d8-9012-e345-6f78901a2b34',
+ seatbid: [
+ {
+ bid: [
+ {
+ id: '1',
+ impid: '1234ab567c89de0',
+ price: 1,
+ adm: '\r\nBB Adserver00:00:51',
+ adid: '67069817',
+ adomain: [
+ 'bluebillywig.com'
+ ],
+ cid: '3535',
+ crid: '67069817',
+ w: 1,
+ h: 1,
+ publicationName: 'bbprebid',
+ accountId: 123,
+ ext: {
+ prebid: {
+ targeting: {
+ hb_bidder: 'bluebillywig',
+ hb_pb: '1.00',
+ hb_size: '1x1'
+ },
+ type: 'video'
+ },
+ bidder: {
+ prebid: {
+ targeting: {
+ hb_bidder: 'bluebillywig',
+ hb_pb: '10.00',
+ hb_size: '1x1'
+ },
+ type: 'video',
+ video: {
+ duration: 51,
+ primary_category: ''
+ }
+ },
+ bidder: {
+ bluebillywig: {
+ brand_id: 1,
+ auction_id: 1,
+ bid_ad_type: 1,
+ creative_info: {
+ video: {
+ duration: 51,
+ mimes: [
+ 'video/x-flv',
+ 'video/mp4',
+ 'video/webm'
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ ],
+ seat: 'bluebillywig'
+ }
+ ],
+ cur: 'USD',
+ ext: {
+ responsetimemillis: {
+ bluebillywig: 0
+ },
+ tmaxrequest: 5000
+ }
+ };
+
+ const serverResponse = { body: validResponse };
+
+ const gdpr = {
+ consentString: 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAA AAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA',
+ gdprApplies: true
+ };
+
+ it('should return empty if no server response', function () {
+ const result = spec.getUserSyncs({}, false, gdpr);
+ expect(result).to.be.empty;
+ });
+
+ it('should return empty if server response is empty', function () {
+ const result = spec.getUserSyncs({}, [], gdpr);
+ expect(result).to.be.empty;
+ });
+
+ it('should return empty if iframeEnabled is not true', () => {
+ const result = spec.getUserSyncs({iframeEnabled: false}, [serverResponse], gdpr);
+ expect(result).to.be.empty;
+ });
+
+ it('should append the various values if they exist', function() {
+ // push data to syncStore
+ spec.buildRequests(validBidRequests, validBidderRequest);
+
+ const result = spec.getUserSyncs({iframeEnabled: true}, [serverResponse], gdpr);
+
+ expect(result).to.not.be.empty;
+
+ expect(result[0].url).to.include('gdpr=1');
+ expect(result[0].url).to.include(gdpr.consentString);
+ expect(result[0].url).to.include('accountId=123');
+ expect(result[0].url).to.include(`bidders=${btoa(JSON.stringify(validBidRequests[0].params.connections))}`);
+ expect(result[0].url).to.include('cb=');
+ });
+ });
+});