diff --git a/modules/kueezBidAdapter.js b/modules/kueezBidAdapter.js
new file mode 100644
index 00000000000..295be160ee9
--- /dev/null
+++ b/modules/kueezBidAdapter.js
@@ -0,0 +1,470 @@
+import { logWarn, logInfo, isArray, isFn, deepAccess, isEmpty, contains, timestamp, getBidIdParameter, triggerPixel, isInteger } from '../src/utils.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { BANNER, VIDEO } from '../src/mediaTypes.js';
+import { config } from '../src/config.js';
+
+const BIDDER_ENDPOINT = 'https://hb.kueezssp.com/hb-kz-multi';
+const BIDDER_TEST_ENDPOINT = 'https://hb.kueezssp.com/hb-multi-kz-test'
+const BIDDER_CODE = 'kueez';
+const MAIN_CURRENCY = 'USD';
+const MEDIA_TYPES = [BANNER, VIDEO];
+const TTL = 420;
+const VERSION = '1.0.0';
+const SUPPORTED_SYNC_METHODS = {
+ IFRAME: 'iframe',
+ PIXEL: 'pixel'
+}
+
+export const spec = {
+ code: BIDDER_CODE,
+ version: VERSION,
+ supportedMediaTypes: MEDIA_TYPES,
+ isBidRequestValid: function (bidRequest) {
+ return validateParams(bidRequest);
+ },
+ buildRequests: function (validBidRequests, bidderRequest) {
+ const [ sharedParams ] = validBidRequests;
+ const testMode = sharedParams.params.testMode;
+ const bidsToSend = prepareBids(validBidRequests, sharedParams, bidderRequest);
+
+ return {
+ method: 'POST',
+ url: getBidderEndpoint(testMode),
+ data: bidsToSend
+ }
+ },
+ interpretResponse: function ({body}) {
+ const bidResponses = body?.bids;
+
+ if (!bidResponses || !bidResponses.length) {
+ return [];
+ }
+
+ return parseBidResponses(bidResponses);
+ },
+ getUserSyncs: function (syncOptions, serverResponses) {
+ const syncs = [];
+ for (const response of serverResponses) {
+ if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) {
+ const pixels = response.body.params.userSyncPixels.map(pixel => {
+ return {
+ type: 'image',
+ url: pixel
+ }
+ })
+ syncs.push(...pixels)
+ }
+ if (syncOptions.iframeEnabled && response.body.params.userSyncURL) {
+ syncs.push({
+ type: 'iframe',
+ url: response.body.params.userSyncURL
+ });
+ }
+ }
+ return syncs;
+ },
+ onBidWon: function (bid) {
+ if (bid == null) {
+ return;
+ }
+
+ logInfo('onBidWon:', bid);
+ if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) {
+ triggerPixel(bid.nurl);
+ }
+ }
+};
+
+registerBidder(spec);
+
+/**
+ * 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 the encoded value
+ * @param val {string}
+ * @returns {string}
+ */
+function getEncodedValIfNotEmpty(val) {
+ return !isEmpty(val) ? encodeURIComponent(val) : '';
+}
+
+/**
+ * get device type
+ * @returns {string}
+ */
+function getDeviceType() {
+ const ua = navigator.userAgent;
+ 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';
+}
+
+/**
+ * Get floor price
+ * @param bid {bid}
+ * @param mediaType {string}
+ * @returns {Number}
+ */
+function getFloorPrice(bid, mediaType) {
+ let floor = 0;
+
+ if (isFn(bid.getFloor)) {
+ let floorResult = bid.getFloor({
+ currency: MAIN_CURRENCY,
+ mediaType: mediaType,
+ size: '*'
+ });
+ floor = floorResult.currency === MAIN_CURRENCY && floorResult.floor ? floorResult.floor : 0;
+ }
+
+ return floor;
+}
+
+/**
+ * Get the ad sizes array from the bid
+ * @param bid {bid}
+ * @param mediaType {string}
+ * @returns {Array}
+ */
+function getSizesArray(bid, mediaType) {
+ let sizes = []
+
+ if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) {
+ sizes = bid.mediaTypes[mediaType].sizes;
+ } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) {
+ sizes = bid.sizes;
+ }
+
+ return sizes;
+}
+
+/**
+ * Get the preferred user-sync method
+ * @param filterSettings {filterSettings}
+ * @param bidderCode {string}
+ * @returns {string}
+ */
+function getSyncMethod(filterSettings, bidderCode) {
+ const iframeConfigs = ['all', 'iframe'];
+ const pixelConfig = 'image';
+ if (filterSettings && iframeConfigs.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) {
+ return SUPPORTED_SYNC_METHODS.IFRAME;
+ }
+ if (!filterSettings || !filterSettings[pixelConfig] || isSyncMethodAllowed(filterSettings[pixelConfig], bidderCode)) {
+ return SUPPORTED_SYNC_METHODS.PIXEL;
+ }
+}
+
+/**
+ * Check sync rule support
+ * @param filterSetting {Object}
+ * @param bidderCode {string}
+ * @returns {boolean}
+ */
+function isSyncMethodAllowed(filterSetting, bidderCode) {
+ if (!filterSetting) {
+ return false;
+ }
+ const bidders = isArray(filterSetting.bidders) ? filterSetting.bidders : [bidderCode];
+ return filterSetting.filter === 'include' && contains(bidders, bidderCode);
+}
+
+/**
+ * Get the bidder endpoint
+ * @param testMode {boolean}
+ * @returns {string}
+ */
+function getBidderEndpoint(testMode) {
+ return testMode ? BIDDER_TEST_ENDPOINT : BIDDER_ENDPOINT;
+}
+
+/**
+ * Generates the bidder parameters
+ * @param validBidRequests {Array}
+ * @param bidderRequest {bidderRequest}
+ * @returns {Array}
+ */
+function generateBidParams(validBidRequests, bidderRequest) {
+ const bidsArray = [];
+
+ if (validBidRequests.length) {
+ validBidRequests.forEach(bid => {
+ bidsArray.push(generateBidParameters(bid, bidderRequest));
+ });
+ }
+
+ return bidsArray;
+}
+
+/**
+ * Generate bid specific parameters
+ * @param bid {bid}
+ * @param bidderRequest {bidderRequest}
+ * @returns {Object} bid specific params object
+ */
+function generateBidParameters(bid, bidderRequest) {
+ const {params} = bid;
+ const mediaType = isBanner(bid) ? BANNER : VIDEO;
+ const sizesArray = getSizesArray(bid, mediaType);
+ const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`);
+ const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`);
+ const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`);
+ const paramsFloorPrice = isNaN(params.floorPrice) ? 0 : params.floorPrice;
+
+ const bidObject = {
+ adUnitCode: getBidIdParameter('adUnitCode', bid),
+ bidId: getBidIdParameter('bidId', bid),
+ bidderRequestId: getBidIdParameter('bidderRequestId', bid),
+ floorPrice: Math.max(getFloorPrice(bid, mediaType), paramsFloorPrice),
+ mediaType,
+ sizes: sizesArray,
+ transactionId: getBidIdParameter('transactionId', bid)
+ };
+
+ if (pos) {
+ bidObject.pos = pos;
+ }
+
+ if (gpid) {
+ bidObject.gpid = gpid;
+ }
+
+ if (placementId) {
+ bidObject.placementId = placementId;
+ }
+
+ if (mediaType === VIDEO) {
+ populateVideoParams(bidObject, bid);
+ }
+
+ return bidObject;
+}
+
+/**
+ * Checks if the media type is a banner
+ * @param bid {bid}
+ * @returns {boolean}
+ */
+function isBanner(bid) {
+ return bid.mediaTypes && bid.mediaTypes.banner;
+}
+
+/**
+ * Generate params that are common between all bids
+ * @param sharedParams {sharedParams}
+ * @param bidderRequest {bidderRequest}
+ * @returns {object} the common params object
+ */
+function generateSharedParams(sharedParams, bidderRequest) {
+ const {bidderCode} = bidderRequest;
+ const {syncEnabled, filterSettings} = config.getConfig('userSync') || {};
+ const domain = window.location.hostname;
+ const generalBidParams = getBidIdParameter('params', sharedParams);
+ const userIds = getBidIdParameter('userId', sharedParams);
+ const ortb2Metadata = config.getConfig('ortb2') || {};
+ const timeout = config.getConfig('bidderTimeout');
+
+ const params = {
+ adapter_version: VERSION,
+ auction_start: timestamp(),
+ device_type: getDeviceType(),
+ dnt: (navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1' || navigator.msDoNotTrack === '1') ? 1 : 0,
+ publisher_id: generalBidParams.org,
+ publisher_name: domain,
+ session_id: getBidIdParameter('auctionId', sharedParams),
+ site_domain: domain,
+ tmax: timeout,
+ ua: navigator.userAgent,
+ wrapper_type: 'prebidjs',
+ wrapper_vendor: '$$PREBID_GLOBAL$$',
+ wrapper_version: '$prebid.version$'
+ }
+
+ if (syncEnabled) {
+ const allowedSyncMethod = getSyncMethod(filterSettings, bidderCode);
+ if (allowedSyncMethod) {
+ params.cs_method = allowedSyncMethod;
+ }
+ }
+
+ if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) {
+ params.gdpr = bidderRequest.gdprConsent.gdprApplies;
+ params.gdpr_consent = bidderRequest.gdprConsent.consentString;
+ }
+
+ if (bidderRequest.uspConsent) {
+ params.us_privacy = bidderRequest.uspConsent;
+ }
+
+ if (generalBidParams.ifa) {
+ params.ifa = generalBidParams.ifa;
+ }
+
+ if (ortb2Metadata.site) {
+ params.site_metadata = JSON.stringify(ortb2Metadata.site);
+ }
+
+ if (ortb2Metadata.user) {
+ params.user_metadata = JSON.stringify(ortb2Metadata.user);
+ }
+
+ if (bidderRequest && bidderRequest.refererInfo) {
+ params.page_url = config.getConfig('pageUrl') || window.location.href;
+ params.referrer = bidderRequest.refererInfo.referer;
+ }
+
+ if (sharedParams.schain) {
+ params.schain = getSupplyChain(sharedParams.schain);
+ }
+
+ if (userIds) {
+ params.userIds = JSON.stringify(userIds);
+ }
+
+ return params
+}
+
+/**
+ * Validates the bidder params
+ * @param bidRequest {bidRequest}
+ * @returns {boolean}
+ */
+function validateParams(bidRequest) {
+ let isValid = true;
+
+ if (!bidRequest.params) {
+ logWarn('Kueez adapter - missing params');
+ isValid = false;
+ }
+
+ if (!bidRequest.params.org) {
+ logWarn('Kueez adapter - org is a required param');
+ isValid = false;
+ }
+
+ return isValid;
+}
+
+/**
+ * Validates the bidder params
+ * @param validBidRequests {Array}
+ * @param sharedParams {sharedParams}
+ * @param bidderRequest {bidderRequest}
+ * @returns {Object}
+ */
+function prepareBids(validBidRequests, sharedParams, bidderRequest) {
+ return {
+ params: generateSharedParams(sharedParams, bidderRequest),
+ bids: generateBidParams(validBidRequests, bidderRequest)
+ }
+}
+
+function getPlaybackMethod(bid) {
+ const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`);
+
+ if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) {
+ return playbackMethod[0];
+ } else if (isInteger(playbackMethod)) {
+ return playbackMethod;
+ }
+}
+
+function populateVideoParams(params, bid) {
+ const linearity = deepAccess(bid, `mediaTypes.video.linearity`);
+ const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`);
+ const minDuration = deepAccess(bid, `mediaTypes.video.minduration`);
+ const placement = deepAccess(bid, `mediaTypes.video.placement`);
+ const playbackMethod = getPlaybackMethod(bid);
+ const skip = deepAccess(bid, `mediaTypes.video.skip`);
+
+ if (linearity) {
+ params.linearity = linearity;
+ }
+
+ if (maxDuration) {
+ params.maxDuration = maxDuration;
+ }
+
+ if (minDuration) {
+ params.minDuration = minDuration;
+ }
+
+ if (placement) {
+ params.placement = placement;
+ }
+
+ if (playbackMethod) {
+ params.playbackMethod = playbackMethod;
+ }
+
+ if (skip) {
+ params.skip = skip;
+ }
+}
+
+/**
+ * Processes the bid responses
+ * @param bids {Array}
+ * @returns {Array}
+ */
+function parseBidResponses(bids) {
+ return bids.map(bid => {
+ const bidResponse = {
+ cpm: bid.cpm,
+ creativeId: bid.requestId,
+ currency: bid.currency || MAIN_CURRENCY,
+ height: bid.height,
+ mediaType: bid.mediaType,
+ meta: {
+ mediaType: bid.mediaType
+ },
+ netRevenue: bid.netRevenue || true,
+ nurl: bid.nurl,
+ requestId: bid.requestId,
+ ttl: bid.ttl || TTL,
+ width: bid.width
+ };
+
+ if (bid.adomain && bid.adomain.length) {
+ bidResponse.meta.advertiserDomains = bid.adomain;
+ }
+
+ if (bid.mediaType === VIDEO) {
+ bidResponse.vastXml = bid.vastXml;
+ } else if (bid.mediaType === BANNER) {
+ bidResponse.ad = bid.ad;
+ }
+
+ return bidResponse;
+ });
+}
diff --git a/modules/kueezBidAdapter.md b/modules/kueezBidAdapter.md
new file mode 100644
index 00000000000..c2afbf0798c
--- /dev/null
+++ b/modules/kueezBidAdapter.md
@@ -0,0 +1,73 @@
+#Overview
+
+Module Name: Kueez Bidder Adapter
+
+Module Type: Bidder Adapter
+
+Maintainer: prebid@kueez.com
+
+# Description
+
+The Kueez adapter requires setup and approval from the Kueez team. Please reach out to prebid@kueez.com for more information.
+
+The adapter supports Banner and Video(instream) media types.
+
+# Bid Parameters
+
+## Video
+
+| Name | Scope | Type | Description | Example
+|---------------| ----- | ---- |-------------------------------------------------------------------| -------
+| `org` | required | String | the organization Id provided by your Kueez representative | "test-publisher-id"
+| `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 1.50
+| `placementId` | optional | String | A unique placement identifier | "12345678"
+| `testMode` | optional | Boolean | This activates the test mode | false
+
+# Test Parameters
+
+```javascript
+var adUnits = [{
+ code: 'banner-div',
+ mediaTypes: {
+ banner: {
+ sizes: [
+ [300, 250],
+ [728, 90]
+ ]
+ }
+ },
+ bids: [{
+ bidder: 'kueez',
+ params: {
+ org: 'test-org-id', // Required
+ floorPrice: 0.10, // Optional
+ placementId: '12345678', // Optional
+ testMode: true // Optional
+ }
+ }]
+},
+ {
+ code: 'dfp-video-div',
+ sizes: [
+ [640, 480]
+ ],
+ mediaTypes: {
+ video: {
+ playerSize: [
+ [640, 480]
+ ],
+ context: 'instream'
+ }
+ },
+ bids: [{
+ bidder: 'kueez',
+ params: {
+ org: 'test-org-id', // Required
+ floorPrice: 2.00, // Optional
+ placementId: '12345678', // Optional
+ testMode: true // Optional
+ }
+ }]
+ }
+];
+```
diff --git a/test/spec/modules/kueezBidAdapter_spec.js b/test/spec/modules/kueezBidAdapter_spec.js
new file mode 100644
index 00000000000..16440965765
--- /dev/null
+++ b/test/spec/modules/kueezBidAdapter_spec.js
@@ -0,0 +1,479 @@
+import { expect } from 'chai';
+import { spec } from 'modules/kueezBidAdapter.js';
+import { newBidder } from 'src/adapters/bidderFactory.js';
+import { config } from 'src/config.js';
+import { BANNER, VIDEO } from '../../../src/mediaTypes.js';
+import * as utils from 'src/utils.js';
+
+const ENDPOINT = 'https://hb.kueezssp.com/hb-kz-multi';
+const TEST_ENDPOINT = 'https://hb.kueezssp.com/hb-multi-kz-test';
+const TTL = 360;
+/* eslint no-console: ["error", { allow: ["log", "warn", "error"] }] */
+
+describe('kueezBidAdapter', 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': 'test-publisher-id'
+ }
+ };
+
+ 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': 'test-publisher-id'
+ },
+ 'bidId': '5wfg9887sd5478',
+ 'bidderRequestId': 'op87952ewq8567',
+ 'auctionId': '87se98rt-5789-8735-2546-t98yh5678231',
+ 'mediaTypes': {
+ 'video': {
+ 'playerSize': [[640, 480]],
+ 'context': 'instream'
+ }
+ },
+ 'vastXml': '"..."'
+ },
+ {
+ 'bidder': spec.code,
+ 'adUnitCode': 'adunit-code',
+ 'sizes': [[300, 250]],
+ 'params': {
+ 'org': 'test-publisher-id'
+ },
+ 'bidId': '5wfg9887sd5478',
+ 'bidderRequestId': 'op87952ewq8567',
+ 'auctionId': '87se98rt-5789-8735-2546-t98yh5678231',
+ 'mediaTypes': {
+ 'banner': {
+ }
+ },
+ 'ad': '"
"'
+ }
+ ];
+
+ const testModeBidRequests = [
+ {
+ 'bidder': spec.code,
+ 'adUnitCode': 'adunit-code',
+ 'sizes': [[640, 480]],
+ 'params': {
+ 'org': 'test-publisher-id',
+ 'testMode': true
+ },
+ 'bidId': '5wfg9887sd5478',
+ 'bidderRequestId': 'op87952ewq8567',
+ 'auctionId': '87se98rt-5789-8735-2546-t98yh5678231',
+ }
+ ];
+
+ const bidderRequest = {
+ bidderCode: 'kueez',
+ }
+ const placementId = '12345678';
+
+ it('sends the placementId to ENDPOINT via POST', function () {
+ bidRequests[0].params.placementId = placementId;
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.bids[0].placementId).to.equal(placementId);
+ });
+
+ it('sends bid request to ENDPOINT via POST', function () {
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.url).to.equal(ENDPOINT);
+ expect(request.method).to.equal('POST');
+ });
+
+ it('sends bid request to TEST ENDPOINT via POST', function () {
+ const request = spec.buildRequests(testModeBidRequests, bidderRequest);
+ expect(request.url).to.equal(TEST_ENDPOINT);
+ expect(request.method).to.equal('POST');
+ });
+
+ it('should send the correct bid Id', function () {
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.bids[0].bidId).to.equal('5wfg9887sd5478');
+ });
+
+ it('should send the correct sizes array', function () {
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.bids[0].sizes).to.be.an('array');
+ expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes)
+ expect(request.data.bids[1].sizes).to.be.an('array');
+ expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes)
+ });
+
+ it('should send the correct media type', function () {
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.bids[0].mediaType).to.equal(VIDEO)
+ expect(request.data.bids[1].mediaType).to.equal(BANNER)
+ });
+
+ it('should respect syncEnabled option', function() {
+ config.setConfig({
+ userSync: {
+ syncEnabled: false,
+ filterSettings: {
+ all: {
+ bidders: '*',
+ filter: 'include'
+ }
+ }
+ }
+ });
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.params).to.be.an('object');
+ expect(request.data.params).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 request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.params).to.be.an('object');
+ expect(request.data.params).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 request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.params).to.be.an('object');
+ expect(request.data.params).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.resetConfig();
+ config.setConfig({
+ userSync: {
+ syncEnabled: true,
+ }
+ });
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.params).to.be.an('object');
+ expect(request.data.params).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 request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.params).to.be.an('object');
+ expect(request.data.params).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 request = spec.buildRequests(bidRequests, bidderRequestWithUSP);
+ expect(request.data.params).to.be.an('object');
+ expect(request.data.params).to.have.property('us_privacy', '1YNN');
+ });
+
+ it('should have an empty us_privacy param if usPrivacy is missing in the bidRequest', function () {
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.params).to.be.an('object');
+ expect(request.data.params).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 request = spec.buildRequests(bidRequests, bidderRequestWithGDPR);
+ expect(request.data.params).to.be.an('object');
+ expect(request.data.params).to.not.have.property('gdpr');
+ expect(request.data.params).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 request = spec.buildRequests(bidRequests, bidderRequestWithGDPR);
+ expect(request.data.params).to.be.an('object');
+ expect(request.data.params).to.have.property('gdpr', true);
+ expect(request.data.params).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 request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.params).to.be.an('object');
+ expect(request.data.params).to.have.property('schain', '1.0,1!indirectseller.com,00001,1,,,');
+ });
+
+ it('should set flooPrice to getFloor.floor value if it is greater than params.floorPrice', function() {
+ const bid = utils.deepClone(bidRequests[0]);
+ bid.getFloor = () => {
+ return {
+ currency: 'USD',
+ floor: 3.32
+ }
+ }
+ bid.params.floorPrice = 0.64;
+ const request = spec.buildRequests([bid], bidderRequest);
+ expect(request.data.bids[0]).to.be.an('object');
+ expect(request.data.bids[0]).to.have.property('floorPrice', 3.32);
+ });
+
+ it('should set floorPrice to params.floorPrice value if it is greater than getFloor.floor', function() {
+ const bid = utils.deepClone(bidRequests[0]);
+ bid.getFloor = () => {
+ return {
+ currency: 'USD',
+ floor: 0.8
+ }
+ }
+ bid.params.floorPrice = 1.5;
+ const request = spec.buildRequests([bid], bidderRequest);
+ expect(request.data.bids[0]).to.be.an('object');
+ expect(request.data.bids[0]).to.have.property('floorPrice', 1.5);
+ });
+ });
+
+ describe('interpretResponse', function () {
+ const response = {
+ params: {
+ currency: 'USD',
+ netRevenue: true,
+ },
+ bids: [{
+ cpm: 12.5,
+ vastXml: '',
+ width: 640,
+ height: 480,
+ requestId: '21e12606d47ba7',
+ adomain: ['abc.com'],
+ mediaType: VIDEO
+ },
+ {
+ cpm: 12.5,
+ ad: '"
"',
+ width: 300,
+ height: 250,
+ requestId: '21e12606d47ba7',
+ adomain: ['abc.com'],
+ mediaType: BANNER
+ }]
+ };
+
+ const expectedVideoResponse = {
+ cpm: 12.5,
+ creativeId: '21e12606d47ba7',
+ currency: 'USD',
+ height: 480,
+ mediaType: VIDEO,
+ meta: {
+ mediaType: VIDEO,
+ advertiserDomains: ['abc.com']
+ },
+ netRevenue: true,
+ nurl: 'http://example.com/win/1234',
+ requestId: '21e12606d47ba7',
+ ttl: TTL,
+ width: 640,
+ vastXml: ''
+ };
+
+ const expectedBannerResponse = {
+ cpm: 12.5,
+ creativeId: '21e12606d47ba7',
+ currency: 'USD',
+ height: 480,
+ mediaType: BANNER,
+ meta: {
+ mediaType: BANNER,
+ advertiserDomains: ['abc.com']
+ },
+ netRevenue: true,
+ nurl: 'http://example.com/win/1234',
+ requestId: '21e12606d47ba7',
+ ttl: TTL,
+ width: 640,
+ ad: '"
"'
+ };
+
+ it('should get correct bid response', function () {
+ const result = spec.interpretResponse({ body: response });
+ expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedVideoResponse));
+ expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedBannerResponse));
+ });
+
+ it('video type should have vastXml key', function () {
+ const result = spec.interpretResponse({ body: response });
+ expect(result[0].vastXml).to.equal(expectedVideoResponse.vastXml)
+ });
+
+ it('banner type should have ad key', function () {
+ const result = spec.interpretResponse({ body: response });
+ expect(result[1].ad).to.equal(expectedBannerResponse.ad)
+ });
+ })
+
+ describe('getUserSyncs', function() {
+ const imageSyncResponse = {
+ body: {
+ params: {
+ userSyncPixels: [
+ 'https://image-sync-url.test/1',
+ 'https://image-sync-url.test/2',
+ 'https://image-sync-url.test/3'
+ ]
+ }
+ }
+ };
+
+ const iframeSyncResponse = {
+ body: {
+ params: {
+ 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([]);
+ });
+ })
+
+ describe('onBidWon', function() {
+ beforeEach(function() {
+ sinon.stub(utils, 'triggerPixel');
+ });
+ afterEach(function() {
+ utils.triggerPixel.restore();
+ });
+
+ it('Should trigger pixel if bid nurl', function() {
+ const bid = {
+ 'bidder': spec.code,
+ 'adUnitCode': 'adunit-code',
+ 'sizes': [['640', '480']],
+ 'nurl': 'http://example.com/win/1234',
+ 'params': {
+ 'org': 'test-publisher-id'
+ }
+ };
+
+ spec.onBidWon(bid);
+ expect(utils.triggerPixel.callCount).to.equal(1)
+ })
+ })
+});