diff --git a/modules/concertAnalyticsAdapter.js b/modules/concertAnalyticsAdapter.js
new file mode 100644
index 00000000000..a81d07e63b5
--- /dev/null
+++ b/modules/concertAnalyticsAdapter.js
@@ -0,0 +1,120 @@
+import {ajax} from '../src/ajax.js';
+import adapter from '../src/AnalyticsAdapter.js';
+import CONSTANTS from '../src/constants.json';
+import adapterManager from '../src/adapterManager.js';
+import * as utils from '../src/utils.js';
+
+const analyticsType = 'endpoint';
+
+// We only want to send about 1% of events for sampling purposes
+const SAMPLE_RATE_PERCENTAGE = 1 / 100;
+const pageIncludedInSample = sampleAnalytics();
+
+const url = 'https://bids.concert.io/analytics';
+
+const {
+ EVENTS: {
+ BID_RESPONSE,
+ BID_WON,
+ AUCTION_END
+ }
+} = CONSTANTS;
+
+let queue = [];
+
+let concertAnalytics = Object.assign(adapter({url, analyticsType}), {
+ track({ eventType, args }) {
+ switch (eventType) {
+ case BID_RESPONSE:
+ if (args.bidder !== 'concert') break;
+ queue.push(mapBidEvent(eventType, args));
+ break;
+
+ case BID_WON:
+ if (args.bidder !== 'concert') break;
+ queue.push(mapBidEvent(eventType, args));
+ break;
+
+ case AUCTION_END:
+ // Set a delay, as BID_WON events will come after AUCTION_END events
+ setTimeout(() => sendEvents(), 3000);
+ break;
+
+ default:
+ break;
+ }
+ }
+});
+
+function mapBidEvent(eventType, args) {
+ const { adId, auctionId, cpm, creativeId, width, height, timeToRespond } = args;
+ const [gamCreativeId, concertRequestId] = getConcertRequestId(creativeId);
+
+ const payload = {
+ event: eventType,
+ concert_rid: concertRequestId,
+ adId,
+ auctionId,
+ creativeId: gamCreativeId,
+ position: args.adUnitCode,
+ url: window.location.href,
+ cpm,
+ width,
+ height,
+ timeToRespond
+ }
+
+ return payload;
+}
+
+/**
+ * In order to pass back the concert_rid from CBS, it is tucked into the `creativeId`
+ * slot in the bid response and combined with a pipe `|`. This method splits the creative ID
+ * and the concert_rid.
+ *
+ * @param {string} creativeId
+ */
+function getConcertRequestId(creativeId) {
+ if (!creativeId || creativeId.indexOf('|') < 0) return [null, null];
+
+ return creativeId.split('|');
+}
+
+function sampleAnalytics() {
+ return Math.random() <= SAMPLE_RATE_PERCENTAGE;
+}
+
+function sendEvents() {
+ concertAnalytics.eventsStorage = queue;
+
+ if (!queue.length) return;
+
+ if (!pageIncludedInSample) {
+ utils.logMessage('Page not included in sample for Concert Analytics');
+ return;
+ }
+
+ try {
+ const body = JSON.stringify(queue);
+ ajax(url, () => queue = [], body, {
+ contentType: 'application/json',
+ method: 'POST'
+ });
+ } catch (err) { utils.logMessage('Concert Analytics error') }
+}
+
+// save the base class function
+concertAnalytics.originEnableAnalytics = concertAnalytics.enableAnalytics;
+concertAnalytics.eventsStorage = [];
+
+// override enableAnalytics so we can get access to the config passed in from the page
+concertAnalytics.enableAnalytics = function (config) {
+ concertAnalytics.originEnableAnalytics(config);
+};
+
+adapterManager.registerAnalyticsAdapter({
+ adapter: concertAnalytics,
+ code: 'concert'
+});
+
+export default concertAnalytics;
diff --git a/modules/concertAnalyticsAdapter.md b/modules/concertAnalyticsAdapter.md
new file mode 100644
index 00000000000..7c9b6d22703
--- /dev/null
+++ b/modules/concertAnalyticsAdapter.md
@@ -0,0 +1,11 @@
+# Overview
+
+```
+Module Name: Concert Analytics Adapter
+Module Type: Analytics Adapter
+Maintainer: support@concert.io
+```
+
+# Description
+
+Analytics adapter for concert.
\ No newline at end of file
diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js
new file mode 100644
index 00000000000..d153ddf9ee2
--- /dev/null
+++ b/modules/concertBidAdapter.js
@@ -0,0 +1,208 @@
+
+import * as utils from '../src/utils.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { getStorageManager } from '../src/storageManager.js'
+
+const BIDDER_CODE = 'concert';
+const CONCERT_ENDPOINT = 'https://bids.concert.io';
+const USER_SYNC_URL = 'https://cdn.concert.io/lib/bids/sync.html';
+
+export const spec = {
+ code: BIDDER_CODE,
+ /**
+ * Determines whether or not the given bid request is valid.
+ *
+ * @param {BidRequest} bid The bid params to validate.
+ * @return boolean True if this is a valid bid, and false otherwise.
+ */
+ isBidRequestValid: function(bid) {
+ if (!bid.params.partnerId) {
+ utils.logWarn('Missing partnerId bid parameter');
+ return false;
+ }
+
+ return true;
+ },
+
+ /**
+ * Make a server request from the list of BidRequests.
+ *
+ * @param {validBidRequests[]} - an array of bids
+ * @param {bidderRequest} -
+ * @return ServerRequest Info describing the request to the server.
+ */
+ buildRequests: function(validBidRequests, bidderRequest) {
+ utils.logMessage(validBidRequests);
+ utils.logMessage(bidderRequest);
+ let payload = {
+ meta: {
+ prebidVersion: '$prebid.version$',
+ pageUrl: bidderRequest.refererInfo.referer,
+ screen: [window.screen.width, window.screen.height].join('x'),
+ debug: utils.debugTurnedOn(),
+ uid: getUid(bidderRequest),
+ optedOut: hasOptedOutOfPersonalization(),
+ adapterVersion: '1.1.0',
+ uspConsent: bidderRequest.uspConsent,
+ gdprConsent: bidderRequest.gdprConsent
+ }
+ }
+
+ payload.slots = validBidRequests.map(bidRequest => {
+ let slot = {
+ name: bidRequest.adUnitCode,
+ bidId: bidRequest.bidId,
+ transactionId: bidRequest.transactionId,
+ sizes: bidRequest.sizes,
+ partnerId: bidRequest.params.partnerId,
+ slotType: bidRequest.params.slotType
+ }
+
+ return slot;
+ });
+
+ utils.logMessage(payload);
+
+ return {
+ method: 'POST',
+ url: `${CONCERT_ENDPOINT}/bids/prebid`,
+ data: JSON.stringify(payload)
+ }
+ },
+ /**
+ * Unpack the response from the server into a list of bids.
+ *
+ * @param {ServerResponse} serverResponse A successful response from the server.
+ * @return {Bid[]} An array of bids which were nested inside the server.
+ */
+ interpretResponse: function(serverResponse, bidRequest) {
+ utils.logMessage(serverResponse);
+ utils.logMessage(bidRequest);
+
+ const serverBody = serverResponse.body;
+
+ if (!serverBody || typeof serverBody !== 'object') {
+ return [];
+ }
+
+ let bidResponses = [];
+
+ bidResponses = serverBody.bids.map(bid => {
+ return {
+ requestId: bid.bidId,
+ cpm: bid.cpm,
+ width: bid.width,
+ height: bid.height,
+ ad: bid.ad,
+ ttl: bid.ttl,
+ creativeId: bid.creativeId,
+ netRevenue: bid.netRevenue,
+ currency: bid.currency
+ }
+ });
+
+ if (utils.debugTurnedOn() && serverBody.debug) {
+ utils.logMessage(`CONCERT`, serverBody.debug);
+ }
+
+ utils.logMessage(bidResponses);
+ return bidResponses;
+ },
+
+ /**
+ * Register the user sync pixels which should be dropped after the auction.
+ *
+ * @param {SyncOptions} syncOptions Which user syncs are allowed?
+ * @param {ServerResponse[]} serverResponses List of server's responses.
+ * @param {gdprConsent} object GDPR consent object.
+ * @param {uspConsent} string US Privacy String.
+ * @return {UserSync[]} The user syncs which should be dropped.
+ */
+ getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) {
+ const syncs = []
+ if (syncOptions.iframeEnabled && !hasOptedOutOfPersonalization()) {
+ let params = [];
+
+ if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) {
+ params.push(`gdpr_applies=${gdprConsent.gdprApplies ? '1' : '0'}`);
+ }
+ if (gdprConsent && (typeof gdprConsent.consentString === 'string')) {
+ params.push(`gdpr_consent=${gdprConsent.consentString}`);
+ }
+ if (uspConsent && (typeof uspConsent === 'string')) {
+ params.push(`usp_consent=${uspConsent}`);
+ }
+
+ syncs.push({
+ type: 'iframe',
+ url: USER_SYNC_URL + (params.length > 0 ? `?${params.join('&')}` : '')
+ });
+ }
+ return syncs;
+ },
+
+ /**
+ * Register bidder specific code, which will execute if bidder timed out after an auction
+ * @param {data} Containing timeout specific data
+ */
+ onTimeout: function(data) {
+ utils.logMessage('concert bidder timed out');
+ utils.logMessage(data);
+ },
+
+ /**
+ * Register bidder specific code, which will execute if a bid from this bidder won the auction
+ * @param {Bid} The bid that won the auction
+ */
+ onBidWon: function(bid) {
+ utils.logMessage('concert bidder won bid');
+ utils.logMessage(bid);
+ }
+
+}
+
+registerBidder(spec);
+
+const storage = getStorageManager();
+
+/**
+ * Check or generate a UID for the current user.
+ */
+function getUid(bidderRequest) {
+ if (hasOptedOutOfPersonalization() || !consentAllowsPpid(bidderRequest)) {
+ return false;
+ }
+
+ const CONCERT_UID_KEY = 'c_uid';
+
+ let uid = storage.getDataFromLocalStorage(CONCERT_UID_KEY);
+
+ if (!uid) {
+ uid = utils.generateUUID();
+ storage.setDataInLocalStorage(CONCERT_UID_KEY, uid);
+ }
+
+ return uid;
+}
+
+/**
+ * Whether the user has opted out of personalization.
+ */
+function hasOptedOutOfPersonalization() {
+ const CONCERT_NO_PERSONALIZATION_KEY = 'c_nap';
+
+ return storage.getDataFromLocalStorage(CONCERT_NO_PERSONALIZATION_KEY) === 'true';
+}
+
+/**
+ * Whether the privacy consent strings allow personalization.
+ *
+ * @param {BidderRequest} bidderRequest Object which contains any data consent signals
+ */
+function consentAllowsPpid(bidderRequest) {
+ /* NOTE: We cannot easily test GDPR consent, without the
+ * `consent-string` npm module; so will have to rely on that
+ * happening on the bid-server. */
+ return !(bidderRequest.uspConsent === 'string' &&
+ bidderRequest.uspConsent.toUpperCase().substring(0, 2) === '1YY')
+}
diff --git a/modules/concertBidAdapter.md b/modules/concertBidAdapter.md
new file mode 100644
index 00000000000..faf774946d1
--- /dev/null
+++ b/modules/concertBidAdapter.md
@@ -0,0 +1,33 @@
+# Overview
+
+```
+Module Name: Concert Bid Adapter
+Module Type: Bidder Adapter
+Maintainer: support@concert.io
+```
+
+# Description
+
+Module that connects to Concert demand sources
+
+# Test Paramters
+```
+ var adUnits = [
+ {
+ code: 'desktop_leaderboard_variable',
+ mediaTypes: {
+ banner: {
+ sizes: [[1030, 590]]
+ }
+ }
+ bids: [
+ {
+ bidder: "concert",
+ params: {
+ partnerId: 'test_partner'
+ }
+ }
+ ]
+ }
+ ];
+```
\ No newline at end of file
diff --git a/test/spec/modules/concertAnalyticsAdapter_spec.js b/test/spec/modules/concertAnalyticsAdapter_spec.js
new file mode 100644
index 00000000000..b0aad2f3156
--- /dev/null
+++ b/test/spec/modules/concertAnalyticsAdapter_spec.js
@@ -0,0 +1,157 @@
+import concertAnalytics from 'modules/concertAnalyticsAdapter.js';
+import { expect } from 'chai';
+const sinon = require('sinon');
+let adapterManager = require('src/adapterManager').default;
+let events = require('src/events');
+let constants = require('src/constants.json');
+
+describe('ConcertAnalyticsAdapter', function() {
+ let sandbox;
+ let xhr;
+ let requests;
+ let clock;
+ let timestamp = 1896134400;
+ let auctionId = '9f894496-10fe-4652-863d-623462bf82b8';
+ let timeout = 1000;
+
+ before(function () {
+ sandbox = sinon.createSandbox();
+ xhr = sandbox.useFakeXMLHttpRequest();
+ requests = [];
+
+ xhr.onCreate = function (request) {
+ requests.push(request);
+ };
+ clock = sandbox.useFakeTimers(1896134400);
+ });
+
+ after(function () {
+ sandbox.restore();
+ });
+
+ describe('track', function() {
+ beforeEach(function () {
+ sandbox.stub(events, 'getEvents').returns([]);
+
+ adapterManager.enableAnalytics({
+ provider: 'concert'
+ });
+ });
+
+ afterEach(function () {
+ events.getEvents.restore();
+ concertAnalytics.eventsStorage = [];
+ concertAnalytics.disableAnalytics();
+ });
+
+ it('should catch all events', function() {
+ sandbox.spy(concertAnalytics, 'track');
+
+ fireBidEvents(events);
+ sandbox.assert.callCount(concertAnalytics.track, 5);
+ });
+
+ it('should report data for BID_RESPONSE, BID_WON events', function() {
+ fireBidEvents(events);
+ clock.tick(3000 + 1000);
+
+ const eventsToReport = ['bidResponse', 'bidWon'];
+ for (var i = 0; i < concertAnalytics.eventsStorage.length; i++) {
+ expect(eventsToReport.indexOf(concertAnalytics.eventsStorage[i].event)).to.be.above(-1);
+ }
+
+ for (var i = 0; i < eventsToReport.length; i++) {
+ expect(concertAnalytics.eventsStorage.some(function(event) {
+ return event.event === eventsToReport[i]
+ })).to.equal(true);
+ }
+ });
+
+ it('should report data in the shape expected by analytics endpoint', function() {
+ fireBidEvents(events);
+ clock.tick(3000 + 1000);
+
+ const requiredFields = ['event', 'concert_rid', 'adId', 'auctionId', 'creativeId', 'position', 'url', 'cpm', 'width', 'height', 'timeToRespond'];
+
+ for (var i = 0; i < requiredFields.length; i++) {
+ expect(concertAnalytics.eventsStorage[0]).to.have.property(requiredFields[i]);
+ }
+ });
+ });
+
+ const adUnits = [{
+ code: 'desktop_leaderboard_variable',
+ sizes: [[1030, 590]],
+ mediaTypes: {
+ banner: {
+ sizes: [[1030, 590]]
+ }
+ },
+ bids: [
+ {
+ bidder: 'concert',
+ params: {
+ partnerId: 'test_partner'
+ }
+ }
+ ]
+ }];
+
+ const bidResponse = {
+ 'bidderCode': 'concert',
+ 'width': 1030,
+ 'height': 590,
+ 'statusMessage': 'Bid available',
+ 'adId': '642f13fe18ab7dc',
+ 'requestId': '4062fba2e039919',
+ 'mediaType': 'banner',
+ 'source': 'client',
+ 'cpm': 6,
+ 'ad': '',
+ 'ttl': 360,
+ 'creativeId': '138308483085|62bac030-a5d3-11ea-b3be-55590c8153a5',
+ 'netRevenue': false,
+ 'currency': 'USD',
+ 'originalCpm': 6,
+ 'originalCurrency': 'USD',
+ 'auctionId': '9f894496-10fe-4652-863d-623462bf82b8',
+ 'responseTimestamp': 1591213790366,
+ 'requestTimestamp': 1591213790017,
+ 'bidder': 'concert',
+ 'adUnitCode': 'desktop_leaderboard_variable',
+ 'timeToRespond': 349,
+ 'status': 'rendered',
+ 'params': [
+ {
+ 'partnerId': 'cst'
+ }
+ ]
+ }
+
+ const bidWon = {
+ 'adId': '642f13fe18ab7dc',
+ 'mediaType': 'banner',
+ 'requestId': '4062fba2e039919',
+ 'cpm': 6,
+ 'creativeId': '138308483085|62bac030-a5d3-11ea-b3be-55590c8153a5',
+ 'currency': 'USD',
+ 'netRevenue': false,
+ 'ttl': 360,
+ 'auctionId': '9f894496-10fe-4652-863d-623462bf82b8',
+ 'statusMessage': 'Bid available',
+ 'responseTimestamp': 1591213790366,
+ 'requestTimestamp': 1591213790017,
+ 'bidder': 'concert',
+ 'adUnitCode': 'desktop_leaderboard_variable',
+ 'sizes': [[1030, 590]],
+ 'size': [1030, 590]
+ }
+
+ function fireBidEvents(events) {
+ events.emit(constants.EVENTS.AUCTION_INIT, {timestamp, auctionId, timeout, adUnits});
+ events.emit(constants.EVENTS.BID_REQUESTED, {bidder: 'concert'});
+ events.emit(constants.EVENTS.BID_RESPONSE, bidResponse);
+ events.emit(constants.EVENTS.AUCTION_END, {});
+ events.emit(constants.EVENTS.BID_WON, bidWon);
+ }
+});
diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js
new file mode 100644
index 00000000000..df999f45df9
--- /dev/null
+++ b/test/spec/modules/concertBidAdapter_spec.js
@@ -0,0 +1,219 @@
+import { expect } from 'chai';
+import sinon from 'sinon';
+import { spec } from 'modules/concertBidAdapter.js';
+import { getStorageManager } from '../../../src/storageManager.js'
+
+describe('ConcertAdapter', function () {
+ let bidRequests;
+ let bidRequest;
+ let bidResponse;
+
+ beforeEach(function () {
+ bidRequests = [
+ {
+ bidder: 'concert',
+ params: {
+ partnerId: 'foo',
+ slotType: 'fizz'
+ },
+ adUnitCode: 'desktop_leaderboard_variable',
+ bidId: 'foo',
+ transactionId: '',
+ sizes: [[1030, 590]]
+ }
+ ];
+
+ bidRequest = {
+ refererInfo: {
+ referer: 'https://www.google.com'
+ },
+ uspConsent: '1YYY',
+ gdprConsent: {}
+ };
+
+ bidResponse = {
+ body: {
+ bids: [
+ {
+ bidId: '16d2e73faea32d9',
+ cpm: '6',
+ width: '1030',
+ height: '590',
+ ad: '',
+ ttl: '360',
+ creativeId: '123349|a7d62700-a4bf-11ea-829f-ad3b0b7a9383',
+ netRevenue: false,
+ currency: 'USD'
+ }
+ ]
+ }
+ }
+ });
+
+ describe('spec.isBidRequestValid', function() {
+ it('should return when it recieved all the required params', function() {
+ const bid = bidRequests[0];
+ expect(spec.isBidRequestValid(bid)).to.equal(true);
+ });
+
+ it('should return false when partner id is missing', function() {
+ const bid = {
+ bidder: 'concert',
+ params: {}
+ }
+
+ expect(spec.isBidRequestValid(bid)).to.equal(false);
+ });
+ });
+
+ describe('spec.buildRequests', function() {
+ it('should build a payload object with the shape expected by server', function() {
+ const request = spec.buildRequests(bidRequests, bidRequest);
+ const payload = JSON.parse(request.data);
+ expect(payload).to.have.property('meta');
+ expect(payload).to.have.property('slots');
+
+ const metaRequiredFields = ['prebidVersion', 'pageUrl', 'screen', 'debug', 'uid', 'optedOut', 'adapterVersion', 'uspConsent', 'gdprConsent'];
+ const slotsRequiredFields = ['name', 'bidId', 'transactionId', 'sizes', 'partnerId', 'slotType'];
+
+ metaRequiredFields.forEach(function(field) {
+ expect(payload.meta).to.have.property(field);
+ });
+ slotsRequiredFields.forEach(function(field) {
+ expect(payload.slots[0]).to.have.property(field);
+ });
+ });
+
+ it('should not generate uid if the user has opted out', function() {
+ const storage = getStorageManager();
+ storage.setDataInLocalStorage('c_nap', 'true');
+ const request = spec.buildRequests(bidRequests, bidRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload.meta.uid).to.equal(false);
+ });
+
+ it('should generate uid if the user has not opted out', function() {
+ const storage = getStorageManager();
+ storage.removeDataFromLocalStorage('c_nap');
+ const request = spec.buildRequests(bidRequests, bidRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload.meta.uid).to.not.equal(false);
+ });
+
+ it('should grab uid from local storage if it exists', function() {
+ const storage = getStorageManager();
+ storage.setDataInLocalStorage('c_uid', 'foo');
+ storage.removeDataFromLocalStorage('c_nap');
+ const request = spec.buildRequests(bidRequests, bidRequest);
+ const payload = JSON.parse(request.data);
+
+ expect(payload.meta.uid).to.equal('foo');
+ });
+ });
+
+ describe('spec.interpretResponse', function() {
+ it('should return bids in the shape expected by prebid', function() {
+ const bids = spec.interpretResponse(bidResponse, bidRequest);
+ const requiredFields = ['requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', 'netRevenue', 'currency'];
+
+ requiredFields.forEach(function(field) {
+ expect(bids[0]).to.have.property(field);
+ });
+ });
+
+ it('should return empty bids if there is no response from server', function() {
+ const bids = spec.interpretResponse({ body: null }, bidRequest);
+ expect(bids).to.have.lengthOf(0);
+ });
+
+ it('should return empty bids if there are no bids from the server', function() {
+ const bids = spec.interpretResponse({ body: {bids: []} }, bidRequest);
+ expect(bids).to.have.lengthOf(0);
+ });
+ });
+
+ describe('spec.getUserSyncs', function() {
+ it('should not register syncs when iframe is not enabled', function() {
+ const opts = {
+ iframeEnabled: false
+ }
+ const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent);
+ expect(sync).to.have.lengthOf(0);
+ });
+
+ it('should not register syncs when the user has opted out', function() {
+ const opts = {
+ iframeEnabled: true
+ };
+ const storage = getStorageManager();
+ storage.setDataInLocalStorage('c_nap', 'true');
+
+ const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent);
+ expect(sync).to.have.lengthOf(0);
+ });
+
+ it('should set gdprApplies flag to 1 if the user is in area where GDPR applies', function() {
+ const opts = {
+ iframeEnabled: true
+ };
+ const storage = getStorageManager();
+ storage.removeDataFromLocalStorage('c_nap');
+
+ bidRequest.gdprConsent = {
+ gdprApplies: true
+ };
+
+ const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent);
+ expect(sync[0].url).to.have.string('gdpr_applies=1');
+ });
+
+ it('should set gdprApplies flag to 1 if the user is in area where GDPR applies', function() {
+ const opts = {
+ iframeEnabled: true
+ };
+ const storage = getStorageManager();
+ storage.removeDataFromLocalStorage('c_nap');
+
+ bidRequest.gdprConsent = {
+ gdprApplies: false
+ };
+
+ const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent);
+ expect(sync[0].url).to.have.string('gdpr_applies=0');
+ });
+
+ it('should set gdpr consent param with the user\'s choices on consent', function() {
+ const opts = {
+ iframeEnabled: true
+ };
+ const storage = getStorageManager();
+ storage.removeDataFromLocalStorage('c_nap');
+
+ bidRequest.gdprConsent = {
+ gdprApplies: false,
+ consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='
+ };
+
+ const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent);
+ expect(sync[0].url).to.have.string('gdpr_consent=BOJ/P2HOJ/P2HABABMAAAAAZ+A==');
+ });
+
+ it('should set ccpa consent param with the user\'s choices on consent', function() {
+ const opts = {
+ iframeEnabled: true
+ };
+ const storage = getStorageManager();
+ storage.removeDataFromLocalStorage('c_nap');
+
+ bidRequest.gdprConsent = {
+ gdprApplies: false,
+ uspConsent: '1YYY'
+ };
+
+ const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent);
+ expect(sync[0].url).to.have.string('usp_consent=1YY');
+ });
+ });
+});