From 4957e3a90ff0cfaaf717d4faf3a8ec3ba7966352 Mon Sep 17 00:00:00 2001 From: Carlos Felix Date: Wed, 10 Nov 2021 11:03:51 -0600 Subject: [PATCH 1/3] SRA support --- modules/33acrossBidAdapter.js | 290 +++++++++----- test/spec/modules/33acrossBidAdapter_spec.js | 378 +++++++++++++++---- 2 files changed, 492 insertions(+), 176 deletions(-) diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 2bdbdd6414b..34bfdb2a28c 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -1,11 +1,19 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { - deepAccess, uniques, isArray, getWindowTop, isGptPubadsDefined, isSlotMatchingAdUnitCode, logInfo, logWarn, + deepAccess, + uniques, + isArray, + getWindowTop, + isGptPubadsDefined, + isSlotMatchingAdUnitCode, + logInfo, + logWarn, getWindowSelf } from '../src/utils.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +// **************************** UTILS *************************** // const BIDDER_CODE = '33across'; const END_POINT = 'https://ssc.33across.com/api/v1/hb'; const SYNC_ENDPOINT = 'https://ssc-cms.33across.com/ps/?m=xch&rt=html&ru=deb'; @@ -42,6 +50,15 @@ const adapterState = { const NON_MEASURABLE = 'nm'; +function getTTXConfig() { + const ttxSettings = Object.assign({}, + config.getConfig('ttxSettings'), + config.getConfig('33across') + ); + + return ttxSettings; +} + // **************************** VALIDATION *************************** // function isBidRequestValid(bid) { return ( @@ -74,6 +91,7 @@ function _validateGUID(bid) { function _validateBanner(bid) { const banner = deepAccess(bid, 'mediaTypes.banner'); + // If there's no banner no need to validate against banner rules if (banner === undefined) { return true; @@ -140,91 +158,125 @@ function _validateVideo(bid) { // NOTE: With regards to gdrp consent data, the server will independently // infer the gdpr applicability therefore, setting the default value to false function buildRequests(bidRequests, bidderRequest) { + const { + ttxSettings, + gdprConsent, + uspConsent, + pageUrl + } = _buildRequestParams(bidRequests, bidderRequest); + + const groupedRequests = _buildRequestGroups(ttxSettings, bidRequests); + + const serverRequests = []; + + for (const key in groupedRequests) { + serverRequests.push( + _createServerRequest({ + bidRequests: groupedRequests[key], + gdprConsent, + uspConsent, + pageUrl, + ttxSettings + }) + ) + } + + return serverRequests; +} + +function _buildRequestParams(bidRequests, bidderRequest) { + const ttxSettings = getTTXConfig(); + const gdprConsent = Object.assign({ consentString: undefined, gdprApplies: false }, bidderRequest && bidderRequest.gdprConsent); const uspConsent = bidderRequest && bidderRequest.uspConsent; + const pageUrl = (bidderRequest && bidderRequest.refererInfo) ? (bidderRequest.refererInfo.referer) : (undefined); adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(uniques); - return bidRequests.map(bidRequest => _createServerRequest( - { - bidRequest, - gdprConsent, - uspConsent, - pageUrl - }) - ); + return { + ttxSettings, + gdprConsent, + uspConsent, + pageUrl + } +} + +function _buildRequestGroups(ttxSettings, bidRequests) { + const bidRequestsComplete = bidRequests.map(_inferProduct); + const enableSRAMode = ttxSettings && ttxSettings.enableSRAMode; + const keyFunc = (enableSRAMode === true) ? _getSRAKey : _getMRAKey; + + return _groupBidRequests(bidRequestsComplete, keyFunc); +} + +function _groupBidRequests(bidRequests, keyFunc) { + const groupedRequests = {}; + + bidRequests.forEach((req) => { + const key = keyFunc(req); + + groupedRequests[key] = groupedRequests[key] || []; + groupedRequests[key].push(req); + }); + + return groupedRequests; +} + +function _getSRAKey(bidRequest) { + return `${bidRequest.params.siteId}:${bidRequest.params.productId}`; +} + +function _getMRAKey(bidRequest) { + return `${bidRequest.bidId}`; } // Infer the necessary data from valid bid for a minimal ttxRequest and create HTTP request -// NOTE: At this point, TTX only accepts request for a single impression -function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl}) { +function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageUrl, ttxSettings }) { const ttxRequest = {}; - const params = bidRequest.params; + const { siteId, test } = bidRequests[0].params; /* * Infer data for the request payload */ - ttxRequest.imp = [{}]; + ttxRequest.imp = []; - if (deepAccess(bidRequest, 'mediaTypes.banner')) { - ttxRequest.imp[0].banner = { - ..._buildBannerORTB(bidRequest) - } - } - - if (deepAccess(bidRequest, 'mediaTypes.video')) { - ttxRequest.imp[0].video = _buildVideoORTB(bidRequest); - } - - ttxRequest.imp[0].ext = { - ttx: { - prod: _getProduct(bidRequest) - } - }; + bidRequests.forEach((req) => { + ttxRequest.imp.push(_createImp(req)); + }); - ttxRequest.site = { id: params.siteId }; + ttxRequest.site = { id: siteId }; if (pageUrl) { ttxRequest.site.page = pageUrl; } - // Go ahead send the bidId in request to 33exchange so it's kept track of in the bid response and - // therefore in ad targetting process - ttxRequest.id = bidRequest.bidId; + ttxRequest.id = bidRequests[0].auctionId; if (gdprConsent.consentString) { - ttxRequest.user = setExtension( - ttxRequest.user, - 'consent', - gdprConsent.consentString - ) + ttxRequest.user = setExtensions(ttxRequest.user, { + 'consent': gdprConsent.consentString + }); } - if (Array.isArray(bidRequest.userIdAsEids) && bidRequest.userIdAsEids.length > 0) { - ttxRequest.user = setExtension( - ttxRequest.user, - 'eids', - bidRequest.userIdAsEids - ) + if (Array.isArray(bidRequests[0].userIdAsEids) && bidRequests[0].userIdAsEids.length > 0) { + ttxRequest.user = setExtensions(ttxRequest.user, { + 'eids': bidRequests[0].userIdAsEids + }); } - ttxRequest.regs = setExtension( - ttxRequest.regs, - 'gdpr', - Number(gdprConsent.gdprApplies) - ); + ttxRequest.regs = setExtensions(ttxRequest.regs, { + 'gdpr': Number(gdprConsent.gdprApplies) + }); if (uspConsent) { - ttxRequest.regs = setExtension( - ttxRequest.regs, - 'us_privacy', - uspConsent - ) + ttxRequest.regs = setExtensions(ttxRequest.regs, { + 'us_privacy': uspConsent + }); } ttxRequest.ext = { @@ -237,16 +289,14 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl } }; - if (bidRequest.schain) { - ttxRequest.source = setExtension( - ttxRequest.source, - 'schain', - bidRequest.schain - ) + if (bidRequests[0].schain) { + ttxRequest.source = setExtensions(ttxRequest.source, { + 'schain': bidRequests[0].schain + }); } // Finally, set the openRTB 'test' param if this is to be a test bid - if (params.test === 1) { + if (test === 1) { ttxRequest.test = 1; } @@ -259,8 +309,7 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl }; // Allow the ability to configure the HB endpoint for testing purposes. - const ttxSettings = config.getConfig('ttxSettings'); - const url = (ttxSettings && ttxSettings.url) || `${END_POINT}?guid=${params.siteId}`; + const url = (ttxSettings && ttxSettings.url) || `${END_POINT}?guid=${siteId}`; // Return the server request return { @@ -272,14 +321,37 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl } // BUILD REQUESTS: SET EXTENSIONS -function setExtension(obj = {}, key, value) { +function setExtensions(obj = {}, extFields) { return Object.assign({}, obj, { - ext: Object.assign({}, obj.ext, { - [key]: value - }) + ext: Object.assign({}, obj.ext, extFields) }); } +// BUILD REQUESTS: IMP +function _createImp(bidRequest) { + const imp = { + id: bidRequest.bidId + }; + + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + imp.banner = { + ..._buildBannerORTB(bidRequest) + } + } + + if (deepAccess(bidRequest, 'mediaTypes.video')) { + imp.video = _buildVideoORTB(bidRequest); + } + + imp.ext = { + ttx: { + prod: deepAccess(bidRequest, 'params.productId') + } + }; + + return imp; +} + // BUILD REQUESTS: SIZE INFERENCE function _transformSizes(sizes) { if (isArray(sizes) && sizes.length === 2 && !isArray(sizes[0])) { @@ -297,6 +369,13 @@ function _getSize(size) { } // BUILD REQUESTS: PRODUCT INFERENCE +function _inferProduct(bidRequest) { + const copy = Object.assign({}, bidRequest); + copy.params.productId = _getProduct(bidRequest); + + return copy; +} + function _getProduct(bidRequest) { const { params, mediaTypes } = bidRequest; @@ -367,7 +446,7 @@ function _buildVideoORTB(bidRequest) { const video = {} - const {w, h} = _getSize(videoParams.playerSize[0]); + const { w, h } = _getSize(videoParams.playerSize[0]); video.w = w; video.h = h; @@ -388,11 +467,11 @@ function _buildVideoORTB(bidRequest) { if (product === PRODUCT.INSTREAM) { video.startdelay = video.startdelay || 0; video.placement = 1; - }; + } // bidfloors if (typeof bidRequest.getFloor === 'function') { - const bidfloors = _getBidFloors(bidRequest, {w: video.w, h: video.h}, VIDEO); + const bidfloors = _getBidFloors(bidRequest, { w: video.w, h: video.h }, VIDEO); if (bidfloors) { Object.assign(video, { @@ -404,6 +483,7 @@ function _buildVideoORTB(bidRequest) { }); } } + return video; } @@ -556,54 +636,68 @@ function _isIframe() { } // **************************** INTERPRET RESPONSE ******************************** // -// NOTE: At this point, the response from 33exchange will only ever contain one bid -// i.e. the highest bid function interpretResponse(serverResponse, bidRequest) { - const bidResponses = []; - - // If there are bids, look at the first bid of the first seatbid (see NOTE above for assumption about ttx) - if (serverResponse.body.seatbid.length > 0 && serverResponse.body.seatbid[0].bid.length > 0) { - bidResponses.push(_createBidResponse(serverResponse.body)); - } + let bidResponses = []; + const { seatbid, cur = 'USD' } = serverResponse.body; + + if (!isArray(seatbid)) { + return bidResponses; + } + + // Pick seats with valid bids and convert them into an Array of responses + // in format expected by Prebid Core + bidResponses = ( + seatbid + .filter((seat) => ( + isArray(seat.bid) && + seat.bid.length > 0 + )) + .map((seat) => { + return ( + seat.bid + .map((bid) => _createBidResponse(bid, cur)) + ); + }) + .flat() + ); return bidResponses; } -// All this assumes that only one bid is ever returned by ttx -function _createBidResponse(response) { +function _createBidResponse(bid, cur) { const isADomainPresent = - response.seatbid[0].bid[0].adomain && response.seatbid[0].bid[0].adomain.length; - const bid = { - requestId: response.id, + bid.adomain && bid.adomain.length; + const bidResponse = { + requestId: bid.impid, bidderCode: BIDDER_CODE, - cpm: response.seatbid[0].bid[0].price, - width: response.seatbid[0].bid[0].w, - height: response.seatbid[0].bid[0].h, - ad: response.seatbid[0].bid[0].adm, - ttl: response.seatbid[0].bid[0].ttl || 60, - creativeId: response.seatbid[0].bid[0].crid, - mediaType: deepAccess(response.seatbid[0].bid[0], 'ext.ttx.mediaType', BANNER), - currency: response.cur, + cpm: bid.price, + width: bid.w, + height: bid.h, + ad: bid.adm, + ttl: bid.ttl || 60, + creativeId: bid.crid, + mediaType: deepAccess(bid, 'ext.ttx.mediaType', BANNER), + currency: cur, netRevenue: true } if (isADomainPresent) { - bid.meta = { - advertiserDomains: response.seatbid[0].bid[0].adomain + bidResponse.meta = { + advertiserDomains: bid.adomain }; } - if (bid.mediaType === VIDEO) { - const vastType = deepAccess(response.seatbid[0].bid[0], 'ext.ttx.vastType', 'xml'); + if (bidResponse.mediaType === VIDEO) { + const vastType = deepAccess(bid, 'ext.ttx.vastType', 'xml'); if (vastType === 'xml') { - bid.vastXml = bid.ad; + bidResponse.vastXml = bidResponse.ad; } else { - bid.vastUrl = bid.ad; + bidResponse.vastUrl = bidResponse.ad; } } - return bid; + return bidResponse; } // **************************** USER SYNC *************************** // diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index b5443cdd5c2..fd62242b924 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -16,20 +16,21 @@ function validateBuiltServerRequest(builtReq, expectedReq) { describe('33acrossBidAdapter:', function () { const BIDDER_CODE = '33across'; const SITE_ID = 'sample33xGUID123456789'; - const PRODUCT_ID = 'siab'; const END_POINT = 'https://ssc.33across.com/api/v1/hb'; let element, win; let bidRequests; let sandbox; - function TtxRequestBuilder() { + function TtxRequestBuilder(siteId = SITE_ID) { const ttxRequest = { - imp: [{}], + imp: [{ + id: 'b1' + }], site: { - id: SITE_ID + id: siteId }, - id: 'b1', + id: 'r1', regs: { ext: { gdpr: 0 @@ -46,66 +47,83 @@ describe('33acrossBidAdapter:', function () { } }; + this.addImp = (id = 'b2') => { + ttxRequest.imp.push({ id }); + + return this; + } + this.withBanner = () => { - Object.assign(ttxRequest.imp[0], { - banner: { - format: [ - { - w: 300, - h: 250 - }, - { - w: 728, - h: 90 - } - ], - ext: { - ttx: { - viewability: { - amount: 100 + ttxRequest.imp.forEach((imp) => { + Object.assign(imp, { + banner: { + format: [ + { + w: 300, + h: 250 + }, + { + w: 728, + h: 90 + } + ], + ext: { + ttx: { + viewability: { + amount: 100 + } } } } - } + }); }); - return this; }; this.withBannerSizes = this.withSizes = sizes => { - Object.assign(ttxRequest.imp[0].banner, { format: sizes }); + ttxRequest.imp.forEach((imp) => { + Object.assign(imp.banner, { format: sizes }); + }); + return this; }; this.withVideo = (params = {}) => { - Object.assign(ttxRequest.imp[0], { - video: { - w: 300, - h: 250, - placement: 2, - ...params - } + ttxRequest.imp.forEach((imp) => { + Object.assign(imp, { + video: { + w: 300, + h: 250, + placement: 2, + ...params + } + }); }); return this; }; this.withViewability = (viewability, format = 'banner') => { - Object.assign(ttxRequest.imp[0][format], { - ext: { - ttx: { viewability } - } + ttxRequest.imp.forEach((imp) => { + Object.assign(imp[format], { + ext: { + ttx: { viewability } + } + }); }); + return this; }; - this.withProduct = (prod = PRODUCT_ID) => { - Object.assign(ttxRequest.imp[0], { - ext: { - ttx: { - prod + this.withProduct = (prod = 'siab') => { + ttxRequest.imp.forEach((imp) => { + Object.assign(imp, { + ext: { + ttx: { + prod + } } - } + }); }); return this; @@ -249,7 +267,7 @@ describe('33acrossBidAdapter:', function () { bidderRequestId: 'b1a', params: { siteId: SITE_ID, - productId: PRODUCT_ID + productId: 'siab' }, adUnitCode: 'div-id', auctionId: 'r1', @@ -258,35 +276,61 @@ describe('33acrossBidAdapter:', function () { } ]; + this.addBid = (bidParams = {}) => { + bidRequests.push({ + bidId: 'b2', + bidder: '33across', + bidderRequestId: 'b1b', + params: { + siteId: SITE_ID, + productId: 'siab' + }, + adUnitCode: 'div-id', + auctionId: 'r1', + mediaTypes: {}, + transactionId: 't2', + ...bidParams + }); + + return this; + }; + this.withBanner = () => { - bidRequests[0].mediaTypes.banner = { - sizes: [ - [300, 250], - [728, 90] - ] - }; + bidRequests.forEach((bid) => { + bid.mediaTypes.banner = { + sizes: [ + [300, 250], + [728, 90] + ] + }; + }); return this; }; this.withProduct = (prod) => { - bidRequests[0].params.productId = prod; - + bidRequests.forEach((bid) => { + bid.params.productId = prod; + }); return this; }; this.withVideo = (params) => { - bidRequests[0].mediaTypes.video = { - playerSize: [[300, 250]], - context: 'outstream', - ...params - }; + bidRequests.forEach((bid) => { + bid.mediaTypes.video = { + playerSize: [[300, 250]], + context: 'outstream', + ...params + }; + }); return this; } this.withUserIds = (eids) => { - bidRequests[0].userIdAsEids = eids; + bidRequests.forEach((bid) => { + bid.userIdAsEids = eids; + }); return this; }; @@ -315,6 +359,7 @@ describe('33acrossBidAdapter:', function () { } }; win = { + parent: null, document: { visibilityState: 'visible' }, @@ -331,7 +376,7 @@ describe('33acrossBidAdapter:', function () { sandbox = sinon.sandbox.create(); sandbox.stub(Date, 'now').returns(1); - sandbox.stub(document, 'getElementById').withArgs('div-id').returns(element); + sandbox.stub(document, 'getElementById').returns(element); sandbox.stub(utils, 'getWindowTop').returns(win); sandbox.stub(utils, 'getWindowSelf').returns(win); }); @@ -1376,10 +1421,146 @@ describe('33acrossBidAdapter:', function () { }); }); }); + + context('when SRA mode is enabled', function() { + it('builds a single request with multiple imps corresponding to each group {siteId, productId}', function() { + sandbox.stub(config, 'getConfig').callsFake(() => { + return { + enableSRAMode: true + } + }); + + const bidRequests = new BidRequestsBuilder() + .addBid() + .addBid({ + bidId: 'b3', + adUnitCode: 'div-id', + params: { + siteId: 'sample33xGUID123456780', + productId: 'siab' + } + }) + .addBid({ + bidId: 'b4', + adUnitCode: 'div-id', + params: { + siteId: 'sample33xGUID123456780', + productId: 'inview' + } + }) + .withBanner() + .withVideo({context: 'outstream'}) + .build(); + + const req1 = new TtxRequestBuilder() + .addImp() + .withBanner() + .withVideo() + .withProduct('siab') + .build(); + + const req2 = new TtxRequestBuilder('sample33xGUID123456780') + .withBanner() + .withVideo() + .withProduct('siab') + .build(); + + req2.imp[0].id = 'b3'; + + const req3 = new TtxRequestBuilder('sample33xGUID123456780') + .withBanner() + .withVideo() + .withProduct('inview') + .build(); + + req3.imp[0].id = 'b4'; + + const serverReq1 = new ServerRequestBuilder() + .withData(req1) + .build(); + + const serverReq2 = new ServerRequestBuilder() + .withData(req2) + .withUrl('https://ssc.33across.com/api/v1/hb?guid=sample33xGUID123456780') + .build(); + + const serverReq3 = new ServerRequestBuilder() + .withData(req3) + .withUrl('https://ssc.33across.com/api/v1/hb?guid=sample33xGUID123456780') + .build(); + + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(builtServerRequests).to.deep.equal([serverReq1, serverReq2, serverReq3]); + }); + }); + + context('when SRA mode is not enabled', function() { + it('builds multiple requests, one corresponding to each Ad Unit', function() { + const bidRequests = new BidRequestsBuilder() + .addBid() + .addBid({ + bidId: 'b3', + adUnitCode: 'div-id', + params: { + siteId: 'sample33xGUID123456780', + productId: 'siab' + } + }) + .withBanner() + .withVideo({context: 'outstream'}) + .build(); + + const req1 = new TtxRequestBuilder() + .withBanner() + .withVideo() + .withProduct('siab') + .build(); + + const req2 = new TtxRequestBuilder() + .withBanner() + .withVideo() + .withProduct('siab') + .build(); + + req2.imp[0].id = 'b2'; + + const req3 = new TtxRequestBuilder('sample33xGUID123456780') + .withBanner() + .withVideo() + .withProduct('siab') + .build(); + + req3.imp[0].id = 'b3'; + + const serverReq1 = new ServerRequestBuilder() + .withData(req1) + .build(); + + const serverReq2 = new ServerRequestBuilder() + .withData(req2) + .build(); + + const serverReq3 = new ServerRequestBuilder() + .withData(req3) + .withUrl('https://ssc.33across.com/api/v1/hb?guid=sample33xGUID123456780') + .build(); + + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(builtServerRequests) + .to.deep.equal([ + serverReq1, + serverReq2, + serverReq3 + ]); + }); + }); }); describe('interpretResponse', function() { let ttxRequest, serverRequest; + const videoBid = ''; beforeEach(function() { ttxRequest = new TtxRequestBuilder() @@ -1390,6 +1571,7 @@ describe('33acrossBidAdapter:', function () { page: 'https://test-url.com' }) .build(); + serverRequest = new ServerRequestBuilder() .withUrl('https://staging-ssc.33across.com/api/v1/hb') .withData(ttxRequest) @@ -1405,11 +1587,12 @@ describe('33acrossBidAdapter:', function () { const serverResponse = { cur: 'USD', ext: {}, - id: 'b1', + id: 'r1', seatbid: [ { bid: [{ id: '1', + impid: 'b1', adm: '

I am an ad

', crid: 1, h: 250, @@ -1441,15 +1624,15 @@ describe('33acrossBidAdapter:', function () { }); it('interprets and returns the single video bid response', function() { - const videoBid = ''; const serverResponse = { cur: 'USD', ext: {}, - id: 'b1', + id: 'r1', seatbid: [ { bid: [{ id: '1', + impid: 'b1', adm: videoBid, ext: { ttx: { @@ -1497,6 +1680,7 @@ describe('33acrossBidAdapter:', function () { { bid: [{ id: '1', + impid: 'b1', adm: '

I am an ad

', crid: 1, h: 250, @@ -1533,7 +1717,7 @@ describe('33acrossBidAdapter:', function () { const serverResponse = { cur: 'USD', ext: {}, - id: 'b1', + id: 'r1', seatbid: [] }; @@ -1542,15 +1726,16 @@ describe('33acrossBidAdapter:', function () { }); context('when more than one bids are returned', function() { - it('interprets and returns the the first bid of the first seatbid', function() { + it('interprets and returns all bids', function() { const serverResponse = { cur: 'USD', ext: {}, - id: 'b1', + id: 'r1', seatbid: [ { bid: [{ id: '1', + impid: 'b1', adm: '

I am an ad

', crid: 1, h: 250, @@ -1559,6 +1744,7 @@ describe('33acrossBidAdapter:', function () { }, { id: '2', + impid: 'b2', adm: '

I am an ad

', crid: 2, h: 250, @@ -1570,7 +1756,14 @@ describe('33acrossBidAdapter:', function () { { bid: [{ id: '3', - adm: '

I am an ad

', + impid: 'b3', + adm: videoBid, + ext: { + ttx: { + mediaType: 'video', + vastType: 'xml' + } + }, crid: 3, h: 250, w: 300, @@ -1579,21 +1772,50 @@ describe('33acrossBidAdapter:', function () { } ] }; - const bidResponse = { - requestId: 'b1', - bidderCode: BIDDER_CODE, - cpm: 0.0940, - width: 300, - height: 250, - ad: '

I am an ad

', - ttl: 60, - creativeId: 1, - mediaType: 'banner', - currency: 'USD', - netRevenue: true - }; + const bidResponse = [ + { + requestId: 'b1', + bidderCode: BIDDER_CODE, + cpm: 0.0940, + width: 300, + height: 250, + ad: '

I am an ad

', + ttl: 60, + creativeId: 1, + mediaType: 'banner', + currency: 'USD', + netRevenue: true + }, + { + requestId: 'b2', + bidderCode: BIDDER_CODE, + cpm: 0.0938, + width: 300, + height: 250, + ad: '

I am an ad

', + ttl: 60, + creativeId: 2, + mediaType: 'banner', + currency: 'USD', + netRevenue: true + }, + { + requestId: 'b3', + bidderCode: BIDDER_CODE, + cpm: 0.0938, + width: 300, + height: 250, + ad: videoBid, + vastXml: '', + ttl: 60, + creativeId: 3, + mediaType: 'video', + currency: 'USD', + netRevenue: true + } + ]; - expect(spec.interpretResponse({ body: serverResponse }, serverRequest)).to.deep.equal([bidResponse]); + expect(spec.interpretResponse({ body: serverResponse }, serverRequest)).to.deep.equal(bidResponse); }); }); }); From 84cb2fd9d6bba1ec8da633eb17085a1bdc7fac0b Mon Sep 17 00:00:00 2001 From: Carlos Felix Date: Wed, 10 Nov 2021 11:29:40 -0600 Subject: [PATCH 2/3] apply code review feedback SRA --- modules/33acrossBidAdapter.js | 64 +++++++++----------- test/spec/modules/33acrossBidAdapter_spec.js | 12 ++-- 2 files changed, 36 insertions(+), 40 deletions(-) diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 34bfdb2a28c..7a1fb1adb98 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -9,7 +9,8 @@ import { isSlotMatchingAdUnitCode, logInfo, logWarn, - getWindowSelf + getWindowSelf, + mergeDeep, } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; @@ -246,7 +247,7 @@ function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageU ttxRequest.imp = []; bidRequests.forEach((req) => { - ttxRequest.imp.push(_createImp(req)); + ttxRequest.imp.push(_buildImpORTB(req)); }); ttxRequest.site = { id: siteId }; @@ -322,15 +323,20 @@ function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageU // BUILD REQUESTS: SET EXTENSIONS function setExtensions(obj = {}, extFields) { - return Object.assign({}, obj, { - ext: Object.assign({}, obj.ext, extFields) + return mergeDeep({}, obj, { + 'ext': extFields }); } // BUILD REQUESTS: IMP -function _createImp(bidRequest) { +function _buildImpORTB(bidRequest) { const imp = { - id: bidRequest.bidId + id: bidRequest.bidId, + ext: { + ttx: { + prod: deepAccess(bidRequest, 'params.productId') + } + } }; if (deepAccess(bidRequest, 'mediaTypes.banner')) { @@ -343,12 +349,6 @@ function _createImp(bidRequest) { imp.video = _buildVideoORTB(bidRequest); } - imp.ext = { - ttx: { - prod: deepAccess(bidRequest, 'params.productId') - } - }; - return imp; } @@ -370,10 +370,11 @@ function _getSize(size) { // BUILD REQUESTS: PRODUCT INFERENCE function _inferProduct(bidRequest) { - const copy = Object.assign({}, bidRequest); - copy.params.productId = _getProduct(bidRequest); - - return copy; + return mergeDeep({}, bidRequest, { + params: { + productId: _getProduct(bidRequest) + } + }); } function _getProduct(bidRequest) { @@ -637,31 +638,26 @@ function _isIframe() { // **************************** INTERPRET RESPONSE ******************************** // function interpretResponse(serverResponse, bidRequest) { - let bidResponses = []; const { seatbid, cur = 'USD' } = serverResponse.body; if (!isArray(seatbid)) { - return bidResponses; + return []; } // Pick seats with valid bids and convert them into an Array of responses // in format expected by Prebid Core - bidResponses = ( - seatbid - .filter((seat) => ( - isArray(seat.bid) && - seat.bid.length > 0 - )) - .map((seat) => { - return ( - seat.bid - .map((bid) => _createBidResponse(bid, cur)) - ); - }) - .flat() - ); - - return bidResponses; + return seatbid + .filter((seat) => ( + isArray(seat.bid) && + seat.bid.length > 0 + )) + .map((seat) => { + return ( + seat.bid + .map((bid) => _createBidResponse(bid, cur)) + ); + }) + .flat() } function _createBidResponse(bid, cur) { diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index fd62242b924..141edc1e61c 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -1454,23 +1454,23 @@ describe('33acrossBidAdapter:', function () { const req1 = new TtxRequestBuilder() .addImp() + .withProduct('siab') .withBanner() .withVideo() - .withProduct('siab') .build(); const req2 = new TtxRequestBuilder('sample33xGUID123456780') + .withProduct('siab') .withBanner() .withVideo() - .withProduct('siab') .build(); req2.imp[0].id = 'b3'; const req3 = new TtxRequestBuilder('sample33xGUID123456780') + .withProduct('inview') .withBanner() .withVideo() - .withProduct('inview') .build(); req3.imp[0].id = 'b4'; @@ -1512,23 +1512,23 @@ describe('33acrossBidAdapter:', function () { .build(); const req1 = new TtxRequestBuilder() + .withProduct('siab') .withBanner() .withVideo() - .withProduct('siab') .build(); const req2 = new TtxRequestBuilder() + .withProduct('siab') .withBanner() .withVideo() - .withProduct('siab') .build(); req2.imp[0].id = 'b2'; const req3 = new TtxRequestBuilder('sample33xGUID123456780') + .withProduct('siab') .withBanner() .withVideo() - .withProduct('siab') .build(); req3.imp[0].id = 'b3'; From 0e04ddec8febe9350e5dcec647a5aa3374247e20 Mon Sep 17 00:00:00 2001 From: Aparna Rao Date: Wed, 10 Nov 2021 15:07:24 -0500 Subject: [PATCH 3/3] removed redundant config check --- modules/33acrossBidAdapter.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 7a1fb1adb98..ef14511f9f9 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -53,8 +53,7 @@ const NON_MEASURABLE = 'nm'; function getTTXConfig() { const ttxSettings = Object.assign({}, - config.getConfig('ttxSettings'), - config.getConfig('33across') + config.getConfig('ttxSettings') ); return ttxSettings;