diff --git a/libraries/equativUtils/equativUtils.js b/libraries/equativUtils/equativUtils.js index bdcbdad2f33..4f0c7b99cab 100644 --- a/libraries/equativUtils/equativUtils.js +++ b/libraries/equativUtils/equativUtils.js @@ -3,6 +3,32 @@ import { deepAccess, isFn } from '../../src/utils.js'; const DEFAULT_FLOOR = 0.0; +/** + * Assigns values to new properties, removes temporary ones from an object + * and remove temporary default bidfloor of -1 + * @param {*} obj An object + * @param {string} key A name of the new property + * @param {string} tempKey A name of the temporary property to be removed + * @returns {*} An updated object + */ +function cleanObject(obj, key, tempKey) { + const newObj = {}; + + for (const prop in obj) { + if (prop === key) { + if (Object.prototype.hasOwnProperty.call(obj, tempKey)) { + newObj[key] = obj[tempKey]; + } + } else if (prop !== tempKey) { + newObj[prop] = obj[prop]; + } + } + + newObj.bidfloor === -1 && delete newObj.bidfloor; + + return newObj; +} + /** * Get floors from Prebid Price Floors module * @@ -11,7 +37,7 @@ const DEFAULT_FLOOR = 0.0; * @param {string} mediaType Bid media type * @return {number} Floor price */ -export function getBidFloor (bid, currency, mediaType) { +export function getBidFloor(bid, currency, mediaType) { const floors = []; if (isFn(bid.getFloor)) { @@ -28,3 +54,102 @@ export function getBidFloor (bid, currency, mediaType) { return floors.length ? Math.min(...floors) : DEFAULT_FLOOR; } + +/** + * Returns a floor price provided by the Price Floors module or the floor price set in the publisher parameters + * @param {*} bid + * @param {string} mediaType A media type + * @param {number} width A width of the ad + * @param {number} height A height of the ad + * @param {string} currency A floor price currency + * @returns {number} Floor price + */ +function getFloor(bid, mediaType, width, height, currency) { + return bid.getFloor?.({ currency, mediaType, size: [width, height] }) + .floor || bid.params.bidfloor || -1; +} + +/** + * Generates a 14-char string id + * @returns {string} + */ +function makeId() { + const length = 14; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let counter = 0; + let str = ''; + + while (counter++ < length) { + str += characters.charAt(Math.floor(Math.random() * characters.length)); + } + + return str; +} + + +/** + * Prepares impressions for the request + * + * @param {*} imps An imps array + * @param {*} bid A bid + * @param {string} currency A currency + * @param {*} impIdMap An impIdMap + * @param {string} adapter A type of adapter (may be 'stx' or 'eqtv') + * @return {*} + */ +export function prepareSplitImps(imps, bid, currency, impIdMap, adapter) { + const splitImps = []; + + imps.forEach(item => { + const floorMap = {}; + + const updateFloorMap = (type, name, width = 0, height = 0) => { + const floor = getFloor(bid, type, width, height, currency); + + if (!floorMap[floor]) { + floorMap[floor] = { + ...item, + bidfloor: floor + }; + } + + if (!floorMap[floor][name]) { + floorMap[floor][name] = type === 'banner' ? { format: [] } : item[type]; + } + + if (type === 'banner') { + floorMap[floor][name].format.push({ w: width, h: height }); + } + }; + + if (item.banner?.format?.length) { + item.banner.format.forEach(format => updateFloorMap('banner', 'bannerTemp', format?.w, format?.h)); + } + + updateFloorMap('native', 'nativeTemp'); + updateFloorMap('video', 'videoTemp', item.video?.w, item.video?.h); + + Object.values(floorMap).forEach(obj => { + [ + ['banner', 'bannerTemp'], + ['native', 'nativeTemp'], + ['video', 'videoTemp'] + ].forEach(([name, tempName]) => obj = cleanObject(obj, name, tempName)); + + if (obj.banner || obj.video || obj.native) { + const id = makeId(); + impIdMap[id] = obj.id; + obj.id = id; + + if (obj.banner && adapter === 'stx') { + obj.banner.pos = item.banner.pos; + obj.banner.topframe = item.banner.topframe; + } + + splitImps.push(obj); + } + }); + }); + + return splitImps; +} diff --git a/modules/equativBidAdapter.js b/modules/equativBidAdapter.js index 0f6648fc509..dddd423b6f4 100644 --- a/modules/equativBidAdapter.js +++ b/modules/equativBidAdapter.js @@ -1,4 +1,5 @@ import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { prepareSplitImps } from '../libraries/equativUtils/equativUtils.js'; import { tryAppendQueryString } from '../libraries/urlUtils/urlUtils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; @@ -23,46 +24,6 @@ let impIdMap = {}; let nwid = 0; let tokens = {}; -/** - * Assigns values to new properties, removes temporary ones from an object - * and remove temporary default bidfloor of -1 - * @param {*} obj An object - * @param {string} key A name of the new property - * @param {string} tempKey A name of the temporary property to be removed - * @returns {*} An updated object - */ -function cleanObject(obj, key, tempKey) { - const newObj = {}; - - for (const prop in obj) { - if (prop === key) { - if (Object.prototype.hasOwnProperty.call(obj, tempKey)) { - newObj[key] = obj[tempKey]; - } - } else if (prop !== tempKey) { - newObj[prop] = obj[prop]; - } - } - - newObj.bidfloor === -1 && delete newObj.bidfloor; - - return newObj; -} - -/** - * Returns a floor price provided by the Price Floors module or the floor price set in the publisher parameters - * @param {*} bid - * @param {string} mediaType A media type - * @param {number} width A width of the ad - * @param {number} height A height of the ad - * @param {string} currency A floor price currency - * @returns {number} Floor price - */ -function getFloor(bid, mediaType, width, height, currency) { - return bid.getFloor?.({ currency, mediaType, size: [width, height] }) - .floor || bid.params.bidfloor || -1; -} - /** * Gets value of the local variable impIdMap * @returns {*} Value of impIdMap @@ -82,23 +43,6 @@ function isValid(bidReq) { return !(bidReq.mediaTypes.video && JSON.stringify(bidReq.mediaTypes.video) === '{}') && !(bidReq.mediaTypes.native && JSON.stringify(bidReq.mediaTypes.native) === '{}'); } -/** - * Generates a 14-char string id - * @returns {string} - */ -function makeId() { - const length = 14; - const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - let counter = 0; - let str = ''; - - while (counter++ < length) { - str += characters.charAt(Math.floor(Math.random() * characters.length)); - } - - return str; -} - /** * Updates bid request with data from previous auction * @param {*} req A bid request object to be updated @@ -154,7 +98,7 @@ export const spec = { requests.push({ data, method: 'POST', - url: 'https://ssb-global.smartadserver.com/api/bid?callerId=169' + url: 'https://ssb-global.smartadserver.com/api/bid?callerId=169', }) }); @@ -229,7 +173,7 @@ export const spec = { }); let url = tryAppendQueryString(COOKIE_SYNC_URL + '?', 'nwid', nwid); - url = tryAppendQueryString(url, 'gdpr', (gdprConsent.gdprApplies ? '1' : '0')); + url = tryAppendQueryString(url, 'gdpr', (gdprConsent?.gdprApplies ? '1' : '0')); return [{ type: 'iframe', url }]; } @@ -268,52 +212,7 @@ export const converter = ortbConverter({ request(buildRequest, imps, bidderRequest, context) { const bid = context.bidRequests[0]; const currency = config.getConfig('currency.adServerCurrency') || 'USD'; - const splitImps = []; - - imps.forEach(item => { - const floorMap = {}; - - const updateFloorMap = (type, name, width = 0, height = 0) => { - const floor = getFloor(bid, type, width, height, currency); - - if (!floorMap[floor]) { - floorMap[floor] = { - ...item, - bidfloor: floor - }; - } - - if (!floorMap[floor][name]) { - floorMap[floor][name] = type === 'banner' ? { format: [] } : item[type]; - } - - if (type === 'banner') { - floorMap[floor][name].format.push({ w: width, h: height }); - } - }; - - if (item.banner?.format?.length) { - item.banner.format.forEach(format => updateFloorMap('banner', 'bannerTemp', format?.w, format?.h)); - } - updateFloorMap('native', 'nativeTemp'); - updateFloorMap('video', 'videoTemp', item.video?.w, item.video?.h); - - Object.values(floorMap).forEach(obj => { - [ - ['banner', 'bannerTemp'], - ['native', 'nativeTemp'], - ['video', 'videoTemp'] - ].forEach(([name, tempName]) => obj = cleanObject(obj, name, tempName)); - - if (obj.banner || obj.video || obj.native) { - const id = makeId(); - impIdMap[id] = obj.id; - obj.id = id; - - splitImps.push(obj); - } - }); - }); + const splitImps = prepareSplitImps(imps, bid, currency, impIdMap, 'eqtv'); let req = buildRequest(splitImps, bidderRequest, context); diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 7144370dc9c..0f348c31148 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -1,23 +1,52 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { prepareSplitImps } from '../libraries/equativUtils/equativUtils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; import { deepAccess, generateUUID, inIframe, isPlainObject, logWarn, mergeDeep } from '../src/utils.js'; const VERSION = '4.3.0'; const BIDDER_CODE = 'sharethrough'; const SUPPLY_ID = 'WYu2BXv1'; +const EQT_ENDPOINT = 'https://ssb.smartadserver.com/api/bid?callerId=233'; const STR_ENDPOINT = `https://btlr.sharethrough.com/universal/v1?supply_id=${SUPPLY_ID}`; const IDENTIFIER_PREFIX = 'Sharethrough:'; +const impIdMap = {}; +let isEqtvTest = null; + +/** + * Gets value of the local variable impIdMap + * @returns {*} Value of impIdMap + */ +export function getImpIdMap() { + return impIdMap; +}; + +/** + * Sets value of the local variable isEqtvTest + * @param {*} value + */ +export function setIsEqtvTest(value) { + isEqtvTest = value; +} + // this allows stubbing of utility function that is used internally by the sharethrough adapter export const sharethroughInternal = { getProtocol, }; +export const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 360 + } +}); + export const sharethroughAdapterSpec = { code: BIDDER_CODE, - supportedMediaTypes: [VIDEO, BANNER], + supportedMediaTypes: [VIDEO, BANNER, NATIVE], gvlid: 80, isBidRequestValid: (bid) => !!bid.params.pkey, @@ -65,6 +94,14 @@ export const sharethroughAdapterSpec = { test: 0, }; + if (bidRequests[0].params.equativNetworkId) { + isEqtvTest = true; + req.site.publisher = { + id: bidRequests[0].params.equativNetworkId, + ...req.site.publisher + }; + } + if (bidderRequest.ortb2?.device?.ext?.cdep) { req.device.ext['cdep'] = bidderRequest.ortb2.device.ext.cdep; } @@ -88,6 +125,7 @@ export const sharethroughAdapterSpec = { if (bidderRequest.uspConsent) { req.regs.ext.us_privacy = bidderRequest.uspConsent; + req.regs.us_privacy = bidderRequest.uspConsent; } if (bidderRequest?.gppConsent?.gppString) { @@ -112,6 +150,7 @@ export const sharethroughAdapterSpec = { const gpid = deepAccess(bidReq, 'ortb2Imp.ext.gpid') || deepAccess(bidReq, 'ortb2Imp.ext.data.pbadslot'); if (gpid) impression.ext.gpid = gpid; + const nativeRequest = deepAccess(bidReq, 'mediaTypes.native'); const videoRequest = deepAccess(bidReq, 'mediaTypes.video'); if (bidderRequest.paapi?.enabled && bidReq.mediaTypes.banner) { @@ -158,25 +197,40 @@ export const sharethroughAdapterSpec = { }; const propertiesToConsider = [ - 'api', 'battr', 'companionad', 'companiontype', 'delivery', 'linearity', 'maxduration', 'mimes', 'minduration', 'placement', 'playbackmethod', 'plcmt', 'protocols', 'skip', 'skipafter', 'skipmin', 'startdelay' - ] + 'api', 'battr', 'companiontype', 'delivery', 'linearity', 'maxduration', 'mimes', 'minduration', 'placement', 'playbackmethod', 'plcmt', 'protocols', 'skip', 'skipafter', 'skipmin', 'startdelay' + ]; + + if (!isEqtvTest) { + propertiesToConsider.push('companionad'); + } propertiesToConsider.forEach(propertyToConsider => { applyVideoProperty(propertyToConsider, videoRequest, impression); }); + } else if (isEqtvTest && nativeRequest) { + const nativeImp = converter.toORTB({ + bidRequests: [bidReq], + bidderRequest + }); + + impression.native = { + ...nativeImp.imp[0].native + }; } else { impression.banner = { pos: deepAccess(bidReq, 'mediaTypes.banner.pos', 0), topframe: inIframe() ? 0 : 1, format: bidReq.sizes.map((size) => ({ w: +size[0], h: +size[1] })), }; - const battr = deepAccess(bidReq, 'mediaTypes.banner.battr', null) || deepAccess(bidReq, 'ortb2Imp.banner.battr') - if (battr) impression.banner.battr = battr + const battr = deepAccess(bidReq, 'mediaTypes.banner.battr', null) || deepAccess(bidReq, 'ortb2Imp.banner.battr'); + if (battr) impression.banner.battr = battr; } + const tagid = isEqtvTest ? bidReq.adUnitCode : String(bidReq.params.pkey); + return { id: bidReq.bidId, - tagid: String(bidReq.params.pkey), + tagid, secure: secure ? 1 : 0, bidfloor: getBidRequestFloor(bidReq), ...impression, @@ -184,13 +238,20 @@ export const sharethroughAdapterSpec = { }) .filter((imp) => !!imp); + let splitImps = [] + if (isEqtvTest) { + const bid = bidRequests[0]; + const currency = config.getConfig('currency.adServerCurrency') || 'USD'; + splitImps = prepareSplitImps(imps, bid, currency, impIdMap, 'stx'); + } + return imps.map((impression) => { return { method: 'POST', - url: STR_ENDPOINT, + url: isEqtvTest ? EQT_ENDPOINT : STR_ENDPOINT, data: { ...req, - imp: [impression], + imp: isEqtvTest ? splitImps : [impression], }, }; }); @@ -209,19 +270,21 @@ export const sharethroughAdapterSpec = { const fledgeAuctionEnabled = body.ext?.auctionConfigs; + const imp = req.data.imp[0]; + const bidsFromExchange = body.seatbid[0].bid.map((bid) => { // Spec: https://docs.prebid.org/dev-docs/bidder-adaptor.html#interpreting-the-response const response = { - requestId: bid.impid, + requestId: isEqtvTest ? impIdMap[bid.impid] : bid.impid, width: +bid.w, height: +bid.h, cpm: +bid.price, creativeId: bid.crid, dealId: bid.dealid || null, - mediaType: req.data.imp[0].video ? VIDEO : BANNER, + mediaType: imp.video ? VIDEO : imp.native ? NATIVE : BANNER, currency: body.cur || 'USD', netRevenue: true, - ttl: 360, + ttl: typeof bid.exp === 'number' && bid.exp > 0 ? bid.exp : 360, ad: bid.adm, nurl: bid.nurl, meta: { @@ -245,12 +308,16 @@ export const sharethroughAdapterSpec = { if (response.mediaType === VIDEO) { response.ttl = 3600; response.vastXml = bid.adm; + } else if (response.mediaType === NATIVE) { + response.native = { + ortb: JSON.parse(bid.adm) + }; } return response; }); - if (fledgeAuctionEnabled) { + if (fledgeAuctionEnabled && !isEqtvTest) { return { bids: bidsFromExchange, paapi: body.ext?.auctionConfigs || {}, @@ -268,13 +335,13 @@ export const sharethroughAdapterSpec = { }, // Empty implementation for prebid core to be able to find it - onTimeout: (data) => {}, + onTimeout: (data) => { }, // Empty implementation for prebid core to be able to find it - onBidWon: (bid) => {}, + onBidWon: (bid) => { }, // Empty implementation for prebid core to be able to find it - onSetTargeting: (bid) => {}, + onSetTargeting: (bid) => { }, }; function getBidRequestFloor(bid) { @@ -289,7 +356,7 @@ function getBidRequestFloor(bid) { floor = parseFloat(floorInfo.floor); } } - return floor !== null ? floor : bid.params.floor; + return floor !== null ? floor : 0; } function getProtocol() { diff --git a/test/spec/libraries/equativUtils/equativUtils_spec.js b/test/spec/libraries/equativUtils/equativUtils_spec.js new file mode 100644 index 00000000000..3056042b2f3 --- /dev/null +++ b/test/spec/libraries/equativUtils/equativUtils_spec.js @@ -0,0 +1,43 @@ +import * as equativUtils from "../../../../libraries/equativUtils/equativUtils"; + +describe('equativUtils', () => { + describe('prepareSplitImps', () => { + let imp, bid; + + beforeEach(() => { + imp = { + id: 'abcd1234', + banner: { + topframe: 1, + pos: 1, + format: [ + { + w: 10, + h: 10, + } + ] + }, + } + + bid = { + params: { + bidfloor: 2.0 + } + } + }) + + it('should not set pos and topframe properties for imp in case of Equativ adapter', () => { + const result = equativUtils.prepareSplitImps([imp], bid, 'USD', {}, 'eqtv')[0]; + + expect(result.banner.pos).to.be.undefined; + expect(result.banner.topframe).to.be.undefined; + }) + + it('should set pos and topframe properties for imp in case of Sharethrough adapter', () => { + const result = equativUtils.prepareSplitImps([imp], bid, 'USD', {}, 'stx')[0]; + + expect(result.banner.pos).to.equal(1); + expect(result.banner.topframe).to.equal(1); + }) + }) +}) diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index 0e0bc7fd14c..b1aa707cb0d 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -5,6 +5,7 @@ import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config'; import * as utils from 'src/utils'; import { deepSetValue } from '../../../src/utils'; +import { getImpIdMap, setIsEqtvTest } from '../../../modules/sharethroughBidAdapter'; const spec = newBidder(sharethroughAdapterSpec).getSpec(); @@ -50,7 +51,134 @@ describe('sharethrough adapter spec', function () { }); describe('open rtb', () => { - let bidRequests, bidderRequest; + let bidRequests, bidderRequest, multiImpBidRequests; + + const bannerBidRequests = [ + { + adUnitCode: 'eqtv_42', + bidId: 'abcd1234', + sizes: [ + [300, 250], + [300, 600], + ], + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + bidder: 'sharethrough', + params: { + pkey: 111, + equativNetworkId: 73 + }, + requestId: 'efgh5678', + ortb2Imp: { + ext: { + tid: 'zsfgzzg', + }, + }, + } + ]; + + const videoBidRequests = [ + { + adUnitCode: 'eqtv_43', + bidId: 'efgh5678', + sizes: [], + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + pos: 3, + skip: 1, + linearity: 1, + minduration: 10, + maxduration: 30, + minbitrate: 300, + maxbitrate: 600, + w: 640, + h: 480, + playbackmethod: [1], + api: [3], + mimes: ['video/x-flv', 'video/mp4'], + startdelay: 42, + battr: [13, 14], + placement: 1, + }, + }, + bidder: 'sharethrough', + params: { + pkey: 111, + equativNetworkIdId: 73 + }, + requestId: 'abcd1234', + ortb2Imp: { + ext: { + tid: 'zsgzgzz', + }, + }, + } + ]; + + const nativeOrtbRequest = { + assets: [{ + id: 0, + required: 1, + title: { + len: 140 + } + }, + { + id: 1, + required: 1, + img: { + type: 3, + w: 300, + h: 600 + } + }, + { + id: 2, + required: 1, + data: { + type: 1 + } + }], + context: 1, + eventtrackers: [{ + event: 1, + methods: [1, 2] + }], + plcmttype: 1, + privacy: 1, + ver: '1.2', + }; + + const nativeBidRequests = [{ + bidder: 'sharethrough', + adUnitCode: 'sharethrough_native_42', + bidId: 'bidId3', + sizes: [], + mediaTypes: { + native: { + ...nativeOrtbRequest + }, + }, + nativeOrtbRequest, + params: { + pkey: 777, + equativNetworkId: 73 + }, + requestId: 'sharethrough_native_reqid_42', + ortb2Imp: { + ext: { + tid: 'sharethrough_native_tid_42', + }, + }, + }] beforeEach(() => { config.setConfig({ @@ -241,6 +369,37 @@ describe('sharethrough adapter spec', function () { }, ]; + multiImpBidRequests = [ + { + adUnitCode: 'equativ_42', + bidId: 'abcd1234', + mediaTypes: { + banner: bannerBidRequests[0].mediaTypes.banner, + video: videoBidRequests[0].mediaTypes.video, + native: nativeBidRequests[0].mediaTypes.native + }, + sizes: [], + nativeOrtbRequest, + bidder: 'sharethrough', + params: { + pkey: 111, + equativNetworkId: 73 + }, + requestId: 'efgh5678', + ortb2Imp: { + ext: { + tid: 'zsfgzzg', + }, + }, + getFloor: ({ mediaType, size }) => { + if ((mediaType === 'banner' && size[0] === 300 && size[1] === 250) || mediaType === 'native') { + return { floor: 1.1 }; + } + return { floor: 0.9 }; + } + } + ]; + bidderRequest = { refererInfo: { ref: 'https://referer.com', @@ -254,6 +413,10 @@ describe('sharethrough adapter spec', function () { }; }); + afterEach(() => { + setIsEqtvTest(null); + }) + describe('buildRequests', function () { describe('top level object', () => { it('should build openRTB request', () => { @@ -421,6 +584,7 @@ describe('sharethrough adapter spec', function () { const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data; expect(openRtbReq.regs.ext.us_privacy).to.equal('consent'); + expect(openRtbReq.regs.us_privacy).to.equal('consent'); }); }); @@ -814,7 +978,7 @@ describe('sharethrough adapter spec', function () { const EXPECTED_AE_VALUE = 1; // ACT - bidderRequest.paapi = {enabled: true}; + bidderRequest.paapi = { enabled: true }; const builtRequests = spec.buildRequests(bidRequests, bidderRequest); const ACTUAL_AE_VALUE = builtRequests[0].data.imp[0].ext.ae; @@ -823,6 +987,106 @@ describe('sharethrough adapter spec', function () { expect(builtRequests[1].data.imp[0].ext.ae).to.be.undefined; }); }); + + describe('isEqtvTest', () => { + it('should set publisher id if equativNetworkId param is present', () => { + const builtRequest = spec.buildRequests(multiImpBidRequests, bidderRequest)[0] + expect(builtRequest.data.site.publisher.id).to.equal(73) + }) + + it('should not set publisher id if equativNetworkId param is not present', () => { + const bidRequest = { + ...bidRequests[0], + params: { + ...bidRequests[0].params, + equativNetworkId: undefined + } + } + + const builtRequest = spec.buildRequests([bidRequest], bidderRequest)[0] + expect(builtRequest.data.site.publisher).to.equal(undefined) + }) + + it('should generate a 14-char id for each imp object', () => { + const request = spec.buildRequests( + bannerBidRequests, + bidderRequest + ); + + request[0].data.imp.forEach(imp => { + expect(imp.id).to.have.lengthOf(14); + }); + }); + + it('should split banner sizes per floor', () => { + const bids = [ + { + ...bannerBidRequests[0], + getFloor: ({ size }) => ({ floor: size[0] * size[1] / 100_000 }) + } + ]; + + const request = spec.buildRequests( + bids, + bidderRequest + ); + + expect(request[0].data.imp).to.have.lengthOf(2); + + const firstImp = request[0].data.imp[0]; + expect(firstImp.bidfloor).to.equal(300 * 250 / 100_000); + expect(firstImp.banner.format).to.have.lengthOf(1); + expect(firstImp.banner.format[0]).to.deep.equal({ w: 300, h: 250 }); + + const secondImp = request[0].data.imp[1]; + expect(secondImp.bidfloor).to.equal(300 * 600 / 100_000); + expect(secondImp.banner.format).to.have.lengthOf(1); + expect(secondImp.banner.format[0]).to.deep.equal({ w: 300, h: 600 }); + }); + + // it('should group media types per floor', () => { + // const request = spec.buildRequests( + // multiImpBidRequests, + // bidderRequest + // ); + + // const firstImp = request[0].data.imp[0]; + + // expect(firstImp.banner.format).to.have.lengthOf(1); + // expect(firstImp.banner.format[0]).to.deep.equal({ w: 300, h: 250 }); + // expect(firstImp).to.have.property('native'); + // expect(firstImp).to.not.have.property('video'); + + // const secondImp = request[0].data.imp[1]; + + // expect(secondImp.banner.format).to.have.lengthOf(1); + // expect(secondImp.banner.format[0]).to.deep.equal({ w: 300, h: 600 }); + // expect(secondImp).to.not.have.property('native'); + // expect(secondImp).to.have.property('video'); + // }); + }) + + it('should return correct native properties from ORTB converter', () => { + if (FEATURES.NATIVE) { + const request = spec.buildRequests(nativeBidRequests, {})[0]; + const assets = JSON.parse(request.data.imp[0].native.request).assets; + + const asset1 = assets[0]; + expect(asset1.id).to.equal(0); + expect(asset1.required).to.equal(1); + expect(asset1.title).to.deep.equal({ 'len': 140 }); + + const asset2 = assets[1]; + expect(asset2.id).to.equal(1); + expect(asset2.required).to.equal(1); + expect(asset2.img).to.deep.equal({ 'type': 3, 'w': 300, 'h': 600 }); + + const asset3 = assets[2]; + expect(asset3.id).to.equal(2); + expect(asset3.required).to.equal(1); + expect(asset3.data).to.deep.equal({ 'type': 1 }) + } + }) }); describe('interpretResponse', function () { @@ -881,6 +1145,144 @@ describe('sharethrough adapter spec', function () { expect(bannerBid.meta.advertiserDomains).to.deep.equal(['domain.com']); expect(bannerBid.vastXml).to.be.undefined; }); + + it('should set requestId from impIdMap when isEqtvTest is true', () => { + setIsEqtvTest(true); + request = spec.buildRequests(bannerBidRequests, bidderRequest)[0] + response = { + body: { + seatbid: [ + { + bid: [ + { + id: 'abcd1234', + impid: 'aaaabbbbccccdd', + w: 300, + h: 250, + price: 42, + crid: 'creative', + dealid: 'deal', + adomain: ['domain.com'], + adm: 'markup', + }, + ], + }, + ], + }, + }; + + const impIdMap = getImpIdMap(); + impIdMap['aaaabbbbccccdd'] = 'abcd1234' + + const resp = spec.interpretResponse(response, request)[0]; + + expect(resp.requestId).to.equal('abcd1234') + }) + + it('should set ttl when bid.exp is a number > 0', () => { + request = spec.buildRequests(bannerBidRequests, bidderRequest)[0] + response = { + body: { + seatbid: [ + { + bid: [ + { + id: 'abcd1234', + impid: 'aaaabbbbccccdd', + w: 300, + h: 250, + price: 42, + crid: 'creative', + dealid: 'deal', + adomain: ['domain.com'], + adm: 'markup', + exp: 100 + }, + ], + }, + ], + }, + }; + + const resp = spec.interpretResponse(response, request)[0]; + expect(resp.ttl).to.equal(100); + }) + + it('should set ttl to 360 when bid.exp is a number <= 0', () => { + request = spec.buildRequests(bannerBidRequests, bidderRequest)[0] + response = { + body: { + seatbid: [ + { + bid: [ + { + id: 'abcd1234', + impid: 'aaaabbbbccccdd', + w: 300, + h: 250, + price: 42, + crid: 'creative', + dealid: 'deal', + adomain: ['domain.com'], + adm: 'markup', + exp: -1 + }, + ], + }, + ], + }, + }; + + const resp = spec.interpretResponse(response, request)[0]; + expect(resp.ttl).to.equal(360); + }) + + it('should return correct properties when fledgeAuctionEnabled is true and isEqtvTest is false', () => { + request = spec.buildRequests(bidRequests, bidderRequest)[0] + response = { + body: { + ext: { + auctionConfigs: { + key: 'value' + } + }, + seatbid: [ + { + bid: [ + { + id: 'abcd1234', + impid: 'aaaabbbbccccdd', + w: 300, + h: 250, + price: 42, + crid: 'creative', + dealid: 'deal', + adomain: ['domain.com'], + adm: 'markup', + exp: -1 + }, + { + id: 'efgh5678', + impid: 'ddeeeeffffgggg', + w: 300, + h: 250, + price: 42, + crid: 'creative', + dealid: 'deal', + adomain: ['domain.com'], + adm: 'markup', + exp: -1 + }, + ], + }, + ], + }, + }; + + const resp = spec.interpretResponse(response, request); + expect(resp.bids.length).to.equal(2); + expect(resp.paapi).to.deep.equal({ 'key': 'value' }) + }) }); describe('video', () => { @@ -926,6 +1328,36 @@ describe('sharethrough adapter spec', function () { }); }); + describe('native', () => { + beforeEach(() => { + request = spec.buildRequests(nativeBidRequests, bidderRequest)[0]; + response = { + body: { + seatbid: [ + { + bid: [ + { + id: '456', + impid: 'bidId2', + w: 640, + h: 480, + price: 42, + adm: '{"ad": "ad"}', + }, + ], + }, + ], + }, + }; + }); + + it('should set correct ortb property', () => { + const resp = spec.interpretResponse(response, request)[0]; + + expect(resp.native.ortb).to.deep.equal({ 'ad': 'ad' }) + }) + }) + describe('meta object', () => { beforeEach(() => { request = spec.buildRequests(bidRequests, bidderRequest)[0];